3070. Count Submatrices with Top-Left Element and Sum Less Than k
Problem Description
You are provided with a matrix of integers, grid
, where indexing begins from 0. Additionally, you are given an integer k
. The task at hand is to determine how many rectangular submatrices (i.e., continuous sections of the grid) include the top-left element of the grid
and have a sum of their elements that does not exceed the value of k
.
This is essentially about finding particular submatrices in a grid. These submatrices must:
- Start from the top-left corner, extending to any end point in the grid.
- Have a total sum of elements that is less than or equal to the given threshold
k
.
Intuition
The solution is built on the concept of prefix sums, a common technique used to quickly calculate the sum of elements in a given subarray, or in this case, submatrices.
In a two-dimensional grid, a prefix sum at a point (i, j)
represents the sum of all elements in the rectangular area from the top-left corner (0, 0)
to the point (i, j)
. The formula for calculating the prefix sum of a point (i, j)
in a matrix is as follows:
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + grid[i][j]
By dynamic programming, this approach builds a matrix of prefix sums, s
, where each cell contains the sum of the submatrix that ends at that cell.
The reason for using the prefix sum technique is that it simplifies the computation of the sum of any submatrix in constant time after the initial calculation of the prefix sum matrix. In essence, once we know the prefix sums, we can deduce the sum of any submatrix by subtracting the relevant prefix sums that overlap.
The provided code implements the following steps:
- First, it initializes a new matrix
s
with dimensions one greater than those ofgrid
in both directions, in order to accommodate the prefix sum without needing to handle bounds checking separately. - It then calculates the two-dimensional prefix sum for each element in
grid
. - For each element
x
ingrid
, we also check if the sum of the submatrix from the top-left corner tox
is less than or equal tok
. If it is, we increment the answer (ans
) by 1. - The answer is the total number of such submatrices, which is what we aim to return.
The insight to take away is that by knowing the prefix sums, it is possible to check very quickly whether each submatrix that starts at the top-left corner satisfies the condition of having a sum less than or equal to k
.
Learn more about Prefix Sum patterns.
Solution Approach
The solution uses the two-dimensional prefix sum concept for efficient computation of submatrix sums. Here is how the algorithm works, step by step:
-
Initialize Prefix Sum Matrix (
s
): A matrixs
is created, initialized with zeros. Its dimensions are one greater than the inputgrid
in both rows and columns. This additional size is for an easier calculation so that the corner cases wheni
orj
is 0 can be handled without condition checking. Essentially, the extra row and column act as padding. -
Calculate Prefix Sums: To calculate the prefix sums, we iterate through each cell in the input
grid
, and for each cell located at(i, j)
ingrid
, we compute the corresponding prefix sum at(i + 1, j + 1)
ins
. The formula used is:s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + x
where
x
is the value atgrid[i - 1][j - 1]
since we are indexings
from1
to include the padding.This step dynamically builds up the prefix sum matrix by adding the current cell's value to the sum of the values above and to the left of the current cell, subtracting the overlapping area that was added twice (which is the cell to the top-left), to maintain the correct sum.
-
Count Valid Submatrices: Each time we calculate a prefix sum for a cell
(i, j)
ins
, we immediately check whether this sum is less than or equal tok
. If it is, it means that the submatrix starting from the top-left corner ofgrid
and extending to(i - 1, j - 1)
has a valid sum; hence, we increment our answerans
by 1.The reason we check after each prefix sum calculation is that we are interested in all the prefix submatrices, and each submatrix is uniquely identified by its bottom-right corner.
In terms of data structures, the additional 2D array s
functions as both storage for the prefix sums as well as the dynamic programming table from which we derive our solution. No other auxiliary data structures are used.
To illustrate the algorithm with an example: consider a 2x2 grid
with values [[1,2], [3,4]]
and k = 6
. The corresponding s
matrix after calculating prefix sums will be:
[[0, 0, 0], [0, 1, 3], [0, 4, 10]]
We will find two valid submatrices, those ending at (1, 1)
and (1,2)
, with sums 1
and 3
, respectively.
Overall, this approach efficiently computes the number of qualifying submatrices in a matrix using dynamic programming and the concept of two-dimensional prefix sums.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's take a small grid
example to illustrate the solution approach:
Suppose we have a grid:
grid = [ [1, 2], [3, 4] ]
and k = 6
.
Following the solution approach let's walk through the algorithm:
-
Initialize Prefix Sum Matrix (
s
): We first create a prefix sum matrixs
with dimensions one greater thangrid
to handle the boundary conditions seamlessly:s = [ [0, 0, 0], [0, 0, 0], [0, 0, 0] ]
-
Calculate Prefix Sums: Now, let’s compute the prefix sums. We iterate over
grid
and updates
starting from the top-left corner:- For
grid[0][0] = 1
, the corresponding cell ins
iss[1][1]
. Hence,s[1][1] = s[0][1] + s[1][0] - s[0][0] + grid[0][0] = 0 + 0 - 0 + 1 = 1
. - For
grid[0][1] = 2
, we calculates[1][2]
:s[1][2] = s[0][2] + s[1][1] - s[0][1] + grid[0][1] = 0 + 1 - 0 + 2 = 3
. - For
grid[1][0] = 3
, we calculates[2][1]
:s[2][1] = s[1][1] + s[2][0] - s[1][0] + grid[1][0] = 1 + 0 - 0 + 3 = 4
. - For
grid[1][1] = 4
, we updates[2][2]
:s[2][2] = s[1][2] + s[2][1] - s[1][1] + grid[1][1] = 3 + 4 - 1 + 4 = 10
.
After calculating, the prefix sums matrix
s
will look like this:s = [ [0, 0, 0], [0, 1, 3], [0, 4, 10] ]
- For
-
Count Valid Submatrices: We now have the prefix sums for all possible submatrices that start from the top-left corner. We consider all points in
s
as the bottom-right corner of potential submatrices and check if the sum is less than or equal tok
:s[1][1] = 1
which is<= k
, so we have one valid submatrix.s[1][2] = 3
which is<= k
, so we have one more valid submatrix.s[2][1] = 4
which is<= k
, but since submatrices must be rectangular, this cell doesn't generate a new submatrix starting from the top-left corner. We do not count it.s[2][2] = 10
which is> k
, so the submatrix with bottom-right corner ats[2][2]
is not a valid submatrix.
We end up with two valid submatrices, those ending at (1, 1)
and (1,2)
, with sums 1
and 3
respectively.
The total number of valid submatrices in this grid
, considering the given k
, is 2
.
Solution Implementation
1class Solution:
2 def countSubmatrices(self, grid: List[List[int]], k: int) -> int:
3 # Initialize a prefix sum matrix with an extra row and column of zeros
4 prefix_sum = [[0] * (len(grid[0]) + 1) for _ in range(len(grid) + 1)]
5 # Initialize counter for the number of submatrices
6 num_submatrices = 0
7
8 # Populate the prefix sum matrix with the cumulative sums
9 for i, row in enumerate(grid, start=1):
10 for j, cell_value in enumerate(row, start=1):
11 # Calculate the cumulative sum up to the current cell
12 prefix_sum[i][j] = (prefix_sum[i - 1][j] + prefix_sum[i][j - 1]
13 - prefix_sum[i - 1][j - 1] + cell_value)
14 # Increment the counter if the sum of the current submatrix is less than or equal to k
15 num_submatrices += prefix_sum[i][j] <= k
16
17 # Return the total number of submatrices with a sum less than or equal to k
18 return num_submatrices
19
1class Solution {
2 public int countSubmatrices(int[][] grid, int k) {
3 int rowCount = grid.length; // The number of rows in the grid.
4 int colCount = grid[0].length; // The number of columns in the grid.
5 // Prefix sum array with extra row and column to handle boundaries.
6 int[][] prefixSum = new int[rowCount + 1][colCount + 1];
7 int count = 0; // Variable to keep track of the count of submatrices.
8
9 // Build the prefix sum matrix.
10 for (int i = 1; i <= rowCount; ++i) {
11 for (int j = 1; j <= colCount; ++j) {
12 // Calculate prefix sum at each cell.
13 prefixSum[i][j] = prefixSum[i - 1][j] + prefixSum[i][j - 1]
14 - prefixSum[i - 1][j - 1] + grid[i - 1][j - 1];
15
16 // If the sum of the current single-cell matrix is less than or equal to k,
17 // increment the count.
18 if (prefixSum[i][j] <= k) {
19 ++count;
20 }
21 }
22 }
23
24 // Return the total count of submatrices.
25 return count;
26 }
27}
28
1#include <vector>
2#include <cstring>
3
4class Solution {
5public:
6 int countSubmatrices(std::vector<std::vector<int>>& grid, int k) {
7 int m = grid.size(); // Row count
8 int n = grid[0].size(); // Column count
9 int prefixSum[m + 1][n + 1]; // Create a 2D array to store prefix sums
10 std::memset(prefixSum, 0, sizeof(prefixSum)); // Initialize prefixSum array with 0
11 int ans = 0; // Initialize counter for submatrices with sum <= k
12
13 // Build prefix sums for submatrices and check if any submatrix equals k
14 for (int i = 1; i <= m; ++i) {
15 for (int j = 1; j <= n; ++j) {
16 // Calculate prefix sum for current submatrix
17 prefixSum[i][j] = prefixSum[i - 1][j] + prefixSum[i][j - 1]
18 - prefixSum[i - 1][j - 1] + grid[i - 1][j - 1];
19
20 // Check if the sum of this single cell submatrix is <= k
21 if (prefixSum[i][j] <= k) {
22 ++ans; // Increment counter if condition is met
23 }
24 }
25 }
26
27 // Return the total number of submatrices with sum <= k
28 return ans;
29 }
30};
31
1// Counts the number of submatrices with a sum less than or equal to k.
2function countSubmatrices(grid: number[][], k: number): number {
3 const numRows = grid.length;
4 const numCols = grid[0].length;
5
6 // Initialize the matrix for storing cumulative sums with extra padding for easy calculations.
7 const cumulativeSums: number[][] = Array.from({ length: numRows + 1 }, () => Array(numCols + 1).fill(0));
8
9 let count: number = 0; // This variable will keep count of our result.
10
11 // Fill the cumulativeSums matrix with the cumulative sum of submatrices.
12 for (let i = 1; i <= numRows; ++i) {
13 for (let j = 1; j <= numCols; ++j) {
14 // Calculate the cumulative sum for the position (i, j).
15 cumulativeSums[i][j] =
16 cumulativeSums[i - 1][j] +
17 cumulativeSums[i][j - 1] -
18 cumulativeSums[i - 1][j - 1] +
19 grid[i - 1][j - 1];
20
21 // If the sum of the submatrix ending at (i-1, j-1) is less than or equal to k,
22 // increment the count.
23 if (cumulativeSums[i][j] <= k) {
24 ++count;
25 }
26 }
27 }
28
29 return count; // Return the final count of submatrices.
30}
31
32// You can then use the function like this:
33// let result = countSubmatrices([[1, 1], [1, 1]], 4);
34// console.log(result); // Outputs the count of submatrices with sum <= k
35
Time and Space Complexity
The given code calculates the number of submatrices in a grid whose sum is less than or equal to k
. It does so by using a 2D prefix sum to compute the sum of any submatrix in constant time after an initial setup phase.
The time complexity of the code cannot be O(m * n)
as the reference answer suggests, because there is a lack of logic to compute submatrices other than those starting at the (0,0) coordinate. For every element (i, j)
in the grid, the algorithm calculates the sum from the starting point (0, 0)
to (i, j)
and increases the count of submatrices if the sum is less than or equal to k
. This process is done in constant time for each element, resulting in a time complexity of O(m * n)
, where m
and n
are the number of rows and columns, respectively. However, this only counts the submatrices starting at (0, 0)
, and does not count all possible submatrices.
The space complexity of the code involves the creation of an auxiliary matrix s
of size (m+1) * (n+1)
to store the prefix sums. Therefore, the space complexity is also O(m * n)
.
To summarize, the actual time complexity for the given code logic should be stated as O(m * n)
for the described prefix sum computation but not for the overall problem of counting all valid submatrices less than k
, and the space complexity is correctly stated as O(m * n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!