🧠
Top-Down DP with Memoization
💡 This approach improves brute force by caching results of subproblems to avoid repeated work, introducing the core DP concept.
Intuition
Define a recursive function that returns the largest square side length ending at (i,j), using memoization to store results.
Algorithm
- Define a recursive function that returns largest square side ending at (i,j).
- If cell is '0', return 0.
- Otherwise, recursively compute left, top, and top-left neighbors.
- Return 1 plus minimum of these neighbors.
- Use memoization to store and reuse results.
- Track maximum side length during recursion.
💡 Memoization avoids recomputing the same states, drastically improving efficiency over brute force.
Recurrence:dp(i,j) = 0 if matrix[i][j] == '0' else 1 + min(dp(i-1,j), dp(i,j-1), dp(i-1,j-1))
def maximalSquare(matrix):
if not matrix or not matrix[0]:
return 0
rows, cols = len(matrix), len(matrix[0])
memo = [[-1]*cols for _ in range(rows)]
max_side = 0
def dfs(i, j):
nonlocal max_side
if i < 0 or j < 0:
return 0
if memo[i][j] != -1:
return memo[i][j]
if matrix[i][j] == '0':
memo[i][j] = 0
else:
left = dfs(i, j-1)
up = dfs(i-1, j)
diag = dfs(i-1, j-1)
memo[i][j] = 1 + min(left, up, diag)
max_side = max(max_side, memo[i][j])
return memo[i][j]
for i in range(rows):
for j in range(cols):
dfs(i, j)
return max_side * max_side
# Example usage:
if __name__ == '__main__':
matrix = [
["1","0","1","0","0"],
["1","0","1","1","1"],
["1","1","1","1","1"],
["1","0","0","1","0"]
]
print(maximalSquare(matrix)) # Output: 4
Line Notes
memo = [[-1]*cols for _ in range(rows)]Initialize memo table with -1 to indicate uncomputed states
def dfs(i, j):Recursive function to compute largest square ending at (i,j)
if memo[i][j] != -1:Return cached result to avoid recomputation
memo[i][j] = 1 + min(left, up, diag)DP recurrence to build solution from neighbors
public class Solution {
private int maxSide = 0;
public int maximalSquare(char[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return 0;
int rows = matrix.length, cols = matrix[0].length;
int[][] memo = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
memo[i][j] = -1;
}
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
dfs(matrix, memo, i, j);
}
}
return maxSide * maxSide;
}
private int dfs(char[][] matrix, int[][] memo, int i, int j) {
if (i < 0 || j < 0) return 0;
if (memo[i][j] != -1) return memo[i][j];
if (matrix[i][j] == '0') {
memo[i][j] = 0;
} else {
int left = dfs(matrix, memo, i, j - 1);
int up = dfs(matrix, memo, i - 1, j);
int diag = dfs(matrix, memo, i - 1, j - 1);
memo[i][j] = 1 + Math.min(left, Math.min(up, diag));
maxSide = Math.max(maxSide, memo[i][j]);
}
return memo[i][j];
}
public static void main(String[] args) {
Solution sol = new Solution();
char[][] matrix = {
{'1','0','1','0','0'},
{'1','0','1','1','1'},
{'1','1','1','1','1'},
{'1','0','0','1','0'}
};
System.out.println(sol.maximalSquare(matrix)); // Output: 4
}
}
Line Notes
int[][] memo = new int[rows][cols];Memo table to store computed results
if (memo[i][j] != -1) return memo[i][j];Return cached result to avoid recomputation
memo[i][j] = 1 + Math.min(left, Math.min(up, diag));Apply DP recurrence relation
maxSide = Math.max(maxSide, memo[i][j]);Track maximum square side length found
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
int maxSide = 0;
int dfs(vector<vector<char>>& matrix, vector<vector<int>>& memo, int i, int j) {
if (i < 0 || j < 0) return 0;
if (memo[i][j] != -1) return memo[i][j];
if (matrix[i][j] == '0') {
memo[i][j] = 0;
} else {
int left = dfs(matrix, memo, i, j - 1);
int up = dfs(matrix, memo, i - 1, j);
int diag = dfs(matrix, memo, i - 1, j - 1);
memo[i][j] = 1 + min({left, up, diag});
maxSide = max(maxSide, memo[i][j]);
}
return memo[i][j];
}
public:
int maximalSquare(vector<vector<char>>& matrix) {
if (matrix.empty() || matrix[0].empty()) return 0;
int rows = matrix.size(), cols = matrix[0].size();
vector<vector<int>> memo(rows, vector<int>(cols, -1));
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
dfs(matrix, memo, i, j);
}
}
return maxSide * maxSide;
}
};
int main() {
Solution sol;
vector<vector<char>> matrix = {
{'1','0','1','0','0'},
{'1','0','1','1','1'},
{'1','1','1','1','1'},
{'1','0','0','1','0'}
};
cout << sol.maximalSquare(matrix) << endl; // Output: 4
return 0;
}
Line Notes
vector<vector<int>> memo(rows, vector<int>(cols, -1));Initialize memo table with -1 for uncomputed states
if (memo[i][j] != -1) return memo[i][j];Return cached result to avoid recomputation
memo[i][j] = 1 + min({left, up, diag});DP recurrence to compute largest square side
maxSide = max(maxSide, memo[i][j]);Update maximum side length found
var maximalSquare = function(matrix) {
if (!matrix || matrix.length === 0 || matrix[0].length === 0) return 0;
const rows = matrix.length, cols = matrix[0].length;
const memo = Array.from({length: rows}, () => Array(cols).fill(-1));
let maxSide = 0;
function dfs(i, j) {
if (i < 0 || j < 0) return 0;
if (memo[i][j] !== -1) return memo[i][j];
if (matrix[i][j] === '0') {
memo[i][j] = 0;
} else {
const left = dfs(i, j - 1);
const up = dfs(i - 1, j);
const diag = dfs(i - 1, j - 1);
memo[i][j] = 1 + Math.min(left, up, diag);
maxSide = Math.max(maxSide, memo[i][j]);
}
return memo[i][j];
}
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
dfs(i, j);
}
}
return maxSide * maxSide;
};
// Example usage:
const matrix = [
['1','0','1','0','0'],
['1','0','1','1','1'],
['1','1','1','1','1'],
['1','0','0','1','0']
];
console.log(maximalSquare(matrix)); // Output: 4
Line Notes
const memo = Array.from({length: rows}, () => Array(cols).fill(-1));Initialize memo table with -1 for uncomputed states
if (memo[i][j] !== -1) return memo[i][j];Return cached result to avoid recomputation
memo[i][j] = 1 + Math.min(left, up, diag);Apply DP recurrence relation
maxSide = Math.max(maxSide, memo[i][j]);Track maximum square side length found
TimeO(m*n) where m = rows and n = cols
SpaceO(m*n) for memoization
Each cell is computed once and cached, avoiding repeated work of brute force.
💡 For a 300x300 matrix, this reduces operations to about 90,000, which is efficient enough.
Interview Verdict: Accepted / Good for understanding recursion + memoization
This approach is efficient and introduces DP concepts clearly, but iterative DP is usually preferred.