SOLUTION:
class Solution {
public:
int countSubmatrices(vector<vector<int>>& grid, int k) {
int n = grid.size();
int m = grid[0].size();
int ans = 0;
vector<vector<int>> dp(n, vector<int>(m,0));
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(i==0 && j==0){
dp[i][j]= grid[i][j];
}
else if(i==0){
dp[i][j] = dp[i][j-1] + grid[i][j];
}
else if(j==0){
dp[i][j] = dp[i-1][j] + grid[i][j];
} else {
dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + grid[i][j];
}
if(dp[i][j]<=k) ans++;
}
}
return ans;
}
};
EASIER ALTERNATIVE SOLUTION:
class Solution {
public:
int countSubmatrices(vector<vector<int>>& grid, int k) {
int n = grid.size();
int m = grid[0].size();
int ans = 0;
vector<vector<int>> dp (n+1, vector<int>(m+1,0));
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + grid[i-1][j-1];
if(dp[i][j]<=k) ans++;
}
}
return ans;
}
};
Problem Understanding
Key Constraint: All valid submatrices must contain the top-left element (grid[0][0]) of the original matrix.
This means every valid submatrix has:
-
Top-left corner at (0, 0)
-
Bottom-right corner at some position (i, j)
Core Insight
Since all valid submatrices start from (0,0), we need to:
-
Find all possible bottom-right corners (i, j)
-
For each (i, j), calculate sum from (0,0) to (i,j)
-
Count how many of these sums are ≤ k
DP Array Construction - Thought Process
Why 2D Prefix Sum?
-
Goal: Calculate sum from (0,0) to any (i,j) efficiently
-
Naive approach: For each (i,j), iterate through all cells from (0,0) to (i,j) → O(n²m²) time
-
Smart approach: Build incrementally using previously computed sums → O(nm) time
Building Intuition
Think of dp[i][j]
as the “total sum of rectangle from top-left corner (0,0) to bottom-right corner (i,j)”
To calculate this sum:
-
Add current element:
grid[i][j]
-
Add rectangle above:
dp[i-1][j]
(sum from (0,0) to (i-1,j)) -
Add rectangle to left:
dp[i][j-1]
(sum from (0,0) to (i,j-1)) -
Subtract overlap:
dp[i-1][j-1]
(counted twice in steps 2&3)
Visual representation:
Edge Cases Handling
-
Base case (0,0):
dp[0][0] = grid[0][0]
- just the single element -
First row: Only add element to the left →
dp[0][j] = dp[0][j-1] + grid[0][j]
-
First column: Only add element above →
dp[i][0] = dp[i-1][0] + grid[i][0]
-
General case: Use full formula with overlap subtraction
Algorithm Breakdown
Step 1: 2D Prefix Sum Construction
vector<vector<int>> dp(n, vector<int>(m,0));
-
dp[i][j]
= sum of all elements from (0,0) to (i,j) -
This represents the sum of the submatrix with top-left at (0,0) and bottom-right at (i,j)
Step 2: Fill DP Array
The standard 2D prefix sum formula:
if(i==0 && j==0){
dp[i][j] = grid[i][j]; // Base case
}
else if(i==0){
dp[i][j] = dp[i][j-1] + grid[i][j]; // First row
}
else if(j==0){
dp[i][j] = dp[i-1][j] + grid[i][j]; // First column
}
else {
dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + grid[i][j];
}
Why this works:
-
dp[i-1][j]
= sum from (0,0) to (i-1,j) -
dp[i][j-1]
= sum from (0,0) to (i,j-1) -
dp[i-1][j-1]
= sum from (0,0) to (i-1,j-1) (counted twice, so subtract) -
grid[i][j]
= current element
Step 3: Count Valid Submatrices
if(dp[i][j] <= k) ans++;
For each position (i,j), if the sum from (0,0) to (i,j) is ≤ k, we found a valid submatrix.
Example Walkthrough
Grid = [[7,6,3], [6,6,1]], k = 18
DP Array Construction:
Position (0,0): dp[0][0] = 7
Position (0,1): dp[0][1] = 7 + 6 = 13
Position (0,2): dp[0][2] = 13 + 3 = 16
Position (1,0): dp[1][0] = 7 + 6 = 13
Position (1,1): dp[1][1] = 7 + 6 + 6 + 6 = 25
Position (1,2): dp[1][2] = 7 + 6 + 3 + 6 + 6 + 1 = 29
Valid Submatrices (sum ≤ 18):
-
(0,0) to (0,0): sum = 7 ✓
-
(0,0) to (0,1): sum = 13 ✓
-
(0,0) to (0,2): sum = 16 ✓
-
(0,0) to (1,0): sum = 13 ✓
-
(0,0) to (1,1): sum = 25 ✗
-
(0,0) to (1,2): sum = 29 ✗
Answer = 4
Time & Space Complexity
-
Time: O(n × m) - single pass through the grid
-
Space: O(n × m) - for the DP array
Key Takeaways
-
Problem constraint simplification: “Contains top-left element” means all submatrices start from (0,0)
-
2D Prefix Sum technique: Efficiently calculate submatrix sums
-
Single pass solution: Build prefix sums and count simultaneously
-
Incremental building: Each dp[i][j] uses previously computed values to avoid redundant calculations
Why This Approach Works
Instead of checking all possible submatrices (which would be O(n²m²)), we leverage the constraint that all valid submatrices must start from (0,0). This reduces the problem to finding all valid bottom-right corners, which can be done in O(nm) time using prefix sums.