At first glance, Maximum Side Length of a Square with Sum Less than or Equal to Threshold looks like a routine matrix problem. But in FAANG and similar top-tier interviews, it is really a test of whether you can recognize hidden inefficiencies in brute-force grid traversal.
Problem statement
You’re given a 2D grid of non-negative integers, mat and a number called threshold. Your task is to find the largest possible square (same number of rows and columns) you can take from the grid such that the sum of all values inside that square is less than or equal to threshold. Return the square’s side length, or 0 if no valid square exists.
Constraints:
m == mat.lengthn == mat[i].length- 1 <=
m,n<= 300 - 0 <=
mat[i][j]<= 104 - 0 <=
threshold<= 105
Examples
| mat | threshold | Output | Explanation |
|---|---|---|---|
| [[2,1,1,0],[1,1,0,1],[1,0,1,1]] | 3 | 2 | The maximum side length is 2 because a 2×2 square like:[[1,1],[1,0]]The above matrix has a sum = 3 (≤ 3), but every 3×3 square has sum > 3. |
| [[5, 1, 1],[1, 1, 1],[1, 1, 1]] | 3 | 1 | A 1×1 square containing 1 works (sum = 1 ≤ 3). But every 2×2 square includes at least three 1s plus something else, so the smallest 2×2 sum is 4, which is > 3. |
| [[3, 3, 3, 3],[3, 3, 3, 3],[3, 3, 3, 3]] | 2 | 0 | Every 1×1 cell is 3, and since 3 > threshold (2), no square can satisfy the condition, so the answer is 0. |
Why checking every square is inefficient
A direct approach would try every top-left corner and every possible side length, then compute each square’s sum by walking all of its cells. The problem is that this repeats an enormous amount of work: neighboring squares overlap heavily, but the naive method still recalculates almost the same totals from scratch each time. On a grid as large as 300×300, the number of candidate squares grows quickly, and once you factor in the cost of re-summing each interior, the runtime becomes unrealistic for interview constraints.
Optimized approach using Prefix sum + Binary search
If we first build a 2D prefix sum array, we can compute the sum of any sub-rectangle (including any square) in constant time. Instead of adding up L×L cells every time we test a square, we use four prefix values to “include–exclude” the area we want.
This eliminates the repeated work found in brute force: each square’s sum becomes an O(1) query, so checking a square no longer depends on its size.
After that, the remaining problem is to find the largest side length that can work. Because all numbers in the grid are non-negative, feasibility has a monotonic behavior: if there exists any square of side length L with sum ≤ threshold, then every smaller length is also feasible (smaller squares contain fewer or equal total value). This monotonic “works / doesn’t work” pattern lets us apply binary search over possible side lengths from 0 to min(m, n).
For each candidate side length L chosen by binary search, we perform a feasibility check: we scan all possible top-left positions (r, c) where an L×L square fits in the matrix, and for each position we compute its square sum using the prefix sums in O(1). If we find even one square whose sum is ≤ threshold, then L is feasible and we try a larger L; otherwise we reduce L. In the end, binary search converges to the maximum side length that satisfies the condition efficiently.
Solution steps
- Let
mandnbe the number of rows and columns inmat. - Build a
(m + 1) × (n + 1)prefix-sum arrayps(with an extra top row and left column of zeros) whereps[i][j]stores the sum of the submatrix from(0, 0)to(i - 1, j - 1). - Define a helper
square_sum(r, c, L)that returns the sum of theL×Lsquare starting at(r, c)using:ps[r+L][c+L] - ps[r][c+L] - ps[r+L][c] + ps[r][c]. - Define
feasible(L):- If
L == 0, returnTrue. - For every top-left position
(r, c)where anL×Lsquare fits (0 ≤ r ≤ m-L,0 ≤ c ≤ n-L), computesquare_sum(r, c, L). - If any square sum
≤ threshold, returnTrue; otherwise returnFalse.
- If
- Binary search the answer
Lbetween0andmin(m, n):- Compute
mid = (lo + hi + 1) // 2. - If
feasible(mid)isTrue, setlo = mid(try bigger squares). - Else set
hi = mid - 1(try smaller squares).
- Compute
- Return
loas the maximum side length that satisfies the threshold.
Let’s look at the following illustration to get a better understanding of the solution.
Solution implementation
Time complexity
Building the 2D prefix sum takes O(m * n) time because each cell is processed once. The binary search performs O(log(min(m, n))) checks, and each feasibility check scans all possible LxL top-left positions, which is O(m * n) in the worst case. Overall, the runtime is O(m * n * log(min(m, n))).
Space complexity
The prefix sum array uses O(m * n) extra space (technically (m+1) * (n+1)), which dominates the memory usage. Other variables use only constant extra space, so worst-case space is O(m * n).
Edge cases
- Threshold too small to fit any cell
Example:mat= [[2]],threshold= 1
Why it works: the feasibility check forL=1fails, so binary search returns0. - Threshold is 0 with zeros present
Example:mat= [[0, 1], [0, 0]],threshold= 0
Why it works: prefix sums correctly compute zero-sum squares; feasibility finds the largest all-zero square. - Single row or single column matrix
Example:mat= [[1, 1, 1]],threshold= 2
Why it works: the maximum square side cannot exceed1, and the search bounds handle that (min(m, n)). - Maximum possible square fits
Example:mat= [[1,1],[1,1]],threshold= 4
Why it works: feasibility will succeed forL=2, and binary search returns the full size.
Common pitfalls
- Off-by-one errors in prefix sum indexing: Using 1-based padding avoids negative indices and makes rectangle queries consistent.
- Incorrect square sum formula: The inclusion-exclusion must subtract the top and left rectangles and add back the overlap.
- Binary search mid calculation: Using the upper-mid
(lo + hi + 1) // 2prevents infinite loops when movingloup. - Not handling
L=0: TreatingL=0as feasible keeps the binary search logic clean and guarantees a valid baseline. - Scanning bounds wrong: The last valid top-left is
(m - L, n - L); missing the+1drops candidates.