1292. Maximum Side Length of a Square with Sum Less than or Equal to Threshold
Problem Description
You are given a m x n matrix mat containing integers and an integer value threshold. Your task is to find the maximum side-length of a square submatrix within mat such that the sum of all elements in that square is less than or equal to threshold.
The problem asks you to:
- Consider all possible square submatrices within the given matrix
- Calculate the sum of elements for each square
- Find the largest square whose sum doesn't exceed the threshold
- Return the side-length of this largest valid square
If no square exists with a sum less than or equal to threshold (including 1x1 squares), return 0.
For example, if you have a 3x3 matrix and threshold of 8, you would check:
- All 1x1 squares (individual elements)
- All 2x2 squares that can fit in the matrix
- All 3x3 squares (in this case, just the entire matrix)
The solution uses:
-
Prefix sum array (
s): A 2D prefix sum array wheres[i][j]represents the sum of all elements in the rectangle from(0,0)to(i-1,j-1). This allows calculating any rectangular sum in O(1) time using the formula:sum = s[i+k][j+k] - s[i][j+k] - s[i+k][j] + s[i][j]for a k×k square starting at(i,j). -
Binary search: Since we're looking for the maximum side-length, binary search is used on the possible side-lengths (from 0 to
min(m,n)). Thecheck(k)function verifies if there exists at least one k×k square with sum ≤ threshold.
The algorithm efficiently finds the answer by combining prefix sums for quick sum calculations with binary search to minimize the number of side-lengths to check.
Intuition
The key insight is recognizing that this problem has a monotonic property: if a square of side-length k exists with sum ≤ threshold, then all squares with side-length less than k will also have valid squares (we can always find smaller squares within the larger valid square). Conversely, if no square of side-length k exists with sum ≤ threshold, then no larger squares will work either.
This monotonic property immediately suggests binary search on the answer. Instead of checking every possible side-length from 1 to min(m,n), we can use binary search to find the maximum valid side-length in O(log(min(m,n))) iterations.
The next challenge is: how do we efficiently check if a square of given side-length k exists with sum ≤ threshold?
A naive approach would calculate the sum for each k×k square by iterating through all its elements, taking O(k²) time per square. With potentially O(m*n) squares to check, this becomes too slow.
This is where 2D prefix sums come in. By preprocessing the matrix to build a prefix sum array, we can calculate the sum of any rectangular region in O(1) time. The formula for a rectangle from (i,j) to (i+k-1, j+k-1) becomes:
sum = s[i+k][j+k] - s[i][j+k] - s[i+k][j] + s[i][j]
This works because:
s[i+k][j+k]gives us the sum from origin to the bottom-right corner- We subtract
s[i][j+k]to remove the region above our square - We subtract
s[i+k][j]to remove the region to the left of our square - We add back
s[i][j]because we subtracted it twice in the previous two operations
Combining these two techniques - binary search for the side-length and prefix sums for quick validation - gives us an efficient solution that runs in O(m*n*log(min(m,n))) time.
Learn more about Binary Search and Prefix Sum patterns.
Solution Implementation
1class Solution:
2 def maxSideLength(self, mat: List[List[int]], threshold: int) -> int:
3 """
4 Find the maximum side length of a square with sum <= threshold.
5 Uses binary search template to find first infeasible side, then return answer - 1.
6
7 For "find maximum" problems, we search for first side where NOT feasible.
8 Pattern: true, true, ..., false, false (find first false, answer is index - 1)
9 Reframed: false, false, ..., true, true (find first true where infeasible)
10 """
11 def feasible(side_length: int) -> bool:
12 """Check if there exists a square of given side length with sum <= threshold."""
13 for row in range(rows - side_length + 1):
14 for col in range(cols - side_length + 1):
15 square_sum = (prefix_sum[row + side_length][col + side_length]
16 - prefix_sum[row][col + side_length]
17 - prefix_sum[row + side_length][col]
18 + prefix_sum[row][col])
19 if square_sum <= threshold:
20 return True
21 return False
22
23 rows, cols = len(mat), len(mat[0])
24
25 # Build prefix sum array
26 prefix_sum = [[0] * (cols + 1) for _ in range(rows + 1)]
27 for i, row in enumerate(mat, start=1):
28 for j, value in enumerate(row, start=1):
29 prefix_sum[i][j] = (prefix_sum[i - 1][j] + prefix_sum[i][j - 1]
30 - prefix_sum[i - 1][j - 1] + value)
31
32 # Binary search: find first side_length where NOT feasible (infeasible)
33 # Then answer = first_true_index - 1
34 left, right = 1, min(rows, cols) + 1 # +1 to include "no valid square" case
35 first_true_index = min(rows, cols) + 1 # Default: all sizes are feasible
36
37 while left <= right:
38 mid = (left + right) // 2
39 if not feasible(mid): # Infeasible (this is our "true" condition)
40 first_true_index = mid
41 right = mid - 1 # Look for smaller infeasible
42 else:
43 left = mid + 1
44
45 # Answer is the largest feasible = first_infeasible - 1
46 return first_true_index - 1
471class Solution {
2 /**
3 * Find maximum side length using binary search template.
4 * For "find maximum" problems, search for first infeasible size.
5 * Pattern: feasible, feasible, ..., infeasible, infeasible
6 * Reframed: find first true where infeasible, answer = firstTrueIndex - 1
7 */
8 private int rows;
9 private int cols;
10 private int threshold;
11 private int[][] prefixSum;
12
13 public int maxSideLength(int[][] mat, int threshold) {
14 rows = mat.length;
15 cols = mat[0].length;
16 this.threshold = threshold;
17
18 // Build prefix sum array
19 prefixSum = new int[rows + 1][cols + 1];
20 for (int i = 1; i <= rows; i++) {
21 for (int j = 1; j <= cols; j++) {
22 prefixSum[i][j] = prefixSum[i - 1][j] + prefixSum[i][j - 1]
23 - prefixSum[i - 1][j - 1] + mat[i - 1][j - 1];
24 }
25 }
26
27 // Binary search: find first infeasible size
28 int left = 1;
29 int right = Math.min(rows, cols) + 1;
30 int firstTrueIndex = Math.min(rows, cols) + 1; // Default: all feasible
31
32 while (left <= right) {
33 int mid = left + (right - left) / 2;
34
35 if (!feasible(mid)) { // Infeasible is our "true" condition
36 firstTrueIndex = mid;
37 right = mid - 1;
38 } else {
39 left = mid + 1;
40 }
41 }
42
43 // Answer is largest feasible = first_infeasible - 1
44 return firstTrueIndex - 1;
45 }
46
47 private boolean feasible(int k) {
48 for (int i = 0; i <= rows - k; i++) {
49 for (int j = 0; j <= cols - k; j++) {
50 int sum = prefixSum[i + k][j + k] - prefixSum[i][j + k]
51 - prefixSum[i + k][j] + prefixSum[i][j];
52 if (sum <= threshold) {
53 return true;
54 }
55 }
56 }
57 return false;
58 }
59}
601class Solution {
2public:
3 /**
4 * Find maximum side length using binary search template.
5 * For "find maximum" problems, search for first infeasible size.
6 * Pattern: feasible, feasible, ..., infeasible, infeasible
7 * Reframed: find first true where infeasible, answer = firstTrueIndex - 1
8 */
9 int maxSideLength(vector<vector<int>>& mat, int threshold) {
10 int rows = mat.size();
11 int cols = mat[0].size();
12
13 // Build prefix sum array
14 vector<vector<int>> prefixSum(rows + 1, vector<int>(cols + 1, 0));
15 for (int i = 1; i <= rows; ++i) {
16 for (int j = 1; j <= cols; ++j) {
17 prefixSum[i][j] = prefixSum[i - 1][j] + prefixSum[i][j - 1]
18 - prefixSum[i - 1][j - 1] + mat[i - 1][j - 1];
19 }
20 }
21
22 // Lambda to check if side length k is feasible
23 auto feasible = [&](int k) -> bool {
24 for (int i = 0; i <= rows - k; ++i) {
25 for (int j = 0; j <= cols - k; ++j) {
26 int sum = prefixSum[i + k][j + k] - prefixSum[i][j + k]
27 - prefixSum[i + k][j] + prefixSum[i][j];
28 if (sum <= threshold) {
29 return true;
30 }
31 }
32 }
33 return false;
34 };
35
36 // Binary search: find first infeasible size
37 int left = 1;
38 int right = min(rows, cols) + 1;
39 int firstTrueIndex = min(rows, cols) + 1; // Default: all feasible
40
41 while (left <= right) {
42 int mid = left + (right - left) / 2;
43
44 if (!feasible(mid)) { // Infeasible is our "true" condition
45 firstTrueIndex = mid;
46 right = mid - 1;
47 } else {
48 left = mid + 1;
49 }
50 }
51
52 // Answer is largest feasible = first_infeasible - 1
53 return firstTrueIndex - 1;
54 }
55};
561/**
2 * Find maximum side length using binary search template.
3 * For "find maximum" problems, search for first infeasible size.
4 * Pattern: feasible, feasible, ..., infeasible, infeasible
5 * Reframed: find first true where infeasible, answer = firstTrueIndex - 1
6 *
7 * @param mat - The input matrix
8 * @param threshold - The maximum allowed sum for a square
9 * @returns The maximum side length of a valid square
10 */
11function maxSideLength(mat: number[][], threshold: number): number {
12 const rows = mat.length;
13 const cols = mat[0].length;
14
15 // Build prefix sum array
16 const prefixSum: number[][] = Array(rows + 1)
17 .fill(0)
18 .map(() => Array(cols + 1).fill(0));
19
20 for (let row = 1; row <= rows; row++) {
21 for (let col = 1; col <= cols; col++) {
22 prefixSum[row][col] = prefixSum[row - 1][col] + prefixSum[row][col - 1]
23 - prefixSum[row - 1][col - 1] + mat[row - 1][col - 1];
24 }
25 }
26
27 // Check if side length k is feasible
28 const feasible = (k: number): boolean => {
29 for (let i = 0; i <= rows - k; i++) {
30 for (let j = 0; j <= cols - k; j++) {
31 const sum = prefixSum[i + k][j + k] - prefixSum[i][j + k]
32 - prefixSum[i + k][j] + prefixSum[i][j];
33 if (sum <= threshold) {
34 return true;
35 }
36 }
37 }
38 return false;
39 };
40
41 // Binary search: find first infeasible size
42 let left = 1;
43 let right = Math.min(rows, cols) + 1;
44 let firstTrueIndex = Math.min(rows, cols) + 1; // Default: all feasible
45
46 while (left <= right) {
47 const mid = Math.floor((left + right) / 2);
48
49 if (!feasible(mid)) { // Infeasible is our "true" condition
50 firstTrueIndex = mid;
51 right = mid - 1;
52 } else {
53 left = mid + 1;
54 }
55 }
56
57 // Answer is largest feasible = first_infeasible - 1
58 return firstTrueIndex - 1;
59}
60Solution Approach
The implementation consists of three main parts: building the prefix sum array, implementing the validation function, and performing binary search.
Step 1: Building the 2D Prefix Sum Array
We create a 2D array s with dimensions (m+1) × (n+1), padded with zeros to handle boundary cases elegantly:
s = [[0] * (n + 1) for _ in range(m + 1)]
for i, row in enumerate(mat, 1):
for j, x in enumerate(row, 1):
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + x
The formula s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + x builds the prefix sum incrementally:
s[i-1][j]: sum of rectangle from origin to rowi-1, columnjs[i][j-1]: sum of rectangle from origin to rowi, columnj-1-s[i-1][j-1]: subtract the overlap (counted twice)+x: add the current elementmat[i-1][j-1]
Step 2: Validation Function check(k)
This function determines if there exists at least one k×k square with sum ≤ threshold:
def check(k: int) -> bool:
for i in range(m - k + 1):
for j in range(n - k + 1):
v = s[i + k][j + k] - s[i][j + k] - s[i + k][j] + s[i][j]
if v <= threshold:
return True
return False
For each possible k×k square starting at position (i,j):
- We calculate its sum using the prefix sum array in O(1) time
- The range
m - k + 1ensures the square fits within matrix boundaries - We return
Trueimmediately upon finding any valid square
Step 3: Binary Search for Maximum Side-Length
l, r = 0, min(m, n)
while l < r:
mid = (l + r + 1) >> 1
if check(mid):
l = mid
else:
r = mid - 1
return l
The binary search works as follows:
- Initialize search range:
l = 0(minimum possible),r = min(m, n)(maximum possible square that fits) - Use
mid = (l + r + 1) >> 1to avoid infinite loops when searching for the maximum value - If
check(mid)returnsTrue, we know squares of sizemidare valid, so search larger sizes:l = mid - Otherwise, the current size is too large, search smaller sizes:
r = mid - 1 - The loop terminates when
l == r, giving us the maximum valid side-length
Time Complexity: O(m*n*log(min(m,n)))
- Building prefix sum:
O(m*n) - Binary search iterations:
O(log(min(m,n))) - Each
check()call:O(m*n)to verify all possible positions
Space Complexity: O(m*n) for the prefix sum array
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given matrix:
mat = [[1, 1, 3], [2, 2, 3], [1, 1, 3]] threshold = 4
Step 1: Build the 2D Prefix Sum Array
We create a prefix sum array s with dimensions 4×4 (padded with zeros):
s = [[0, 0, 0, 0], [0, 1, 2, 5], [0, 3, 6, 12], [0, 4, 8, 17]]
Let's trace how we calculate s[2][2]:
s[2][2] = s[1][2] + s[2][1] - s[1][1] + mat[1][1]s[2][2] = 2 + 3 - 1 + 2 = 6
This represents the sum of all elements from (0,0) to (1,1) in the original matrix.
Step 2: Binary Search on Side-Length
Initial range: l = 0, r = min(3, 3) = 3
First iteration: mid = (0 + 3 + 1) >> 1 = 2
- Check if any 2×2 square has sum ≤ 4:
- Square at (0,0):
sum = s[2][2] - s[0][2] - s[2][0] + s[0][0] = 6 - 0 - 0 + 0 = 6(> 4, invalid) - Square at (0,1):
sum = s[2][3] - s[0][3] - s[2][1] + s[0][1] = 12 - 0 - 3 + 0 = 9(> 4, invalid) - Square at (1,0):
sum = s[3][2] - s[1][2] - s[3][0] + s[1][0] = 8 - 2 - 0 + 0 = 6(> 4, invalid) - Square at (1,1):
sum = s[3][3] - s[1][3] - s[3][1] + s[1][1] = 17 - 5 - 4 + 1 = 9(> 4, invalid)
- Square at (0,0):
check(2)returnsFalse, sor = mid - 1 = 1
Second iteration: mid = (0 + 1 + 1) >> 1 = 1
- Check if any 1×1 square has sum ≤ 4:
- Square at (0,0):
sum = s[1][1] - s[0][1] - s[1][0] + s[0][0] = 1 - 0 - 0 + 0 = 1(≤ 4, valid!)
- Square at (0,0):
check(1)returnsTrue, sol = mid = 1
Termination: l = r = 1, return 1
The maximum side-length of a square with sum ≤ 4 is 1.
Verification of the prefix sum formula: Let's verify the 2×2 square at position (0,0) manually:
- Elements: mat[0][0] + mat[0][1] + mat[1][0] + mat[1][1] = 1 + 1 + 2 + 2 = 6
- Using prefix sum: s[2][2] - s[0][2] - s[2][0] + s[0][0] = 6 - 0 - 0 + 0 = 6 ✓
The algorithm efficiently found that only 1×1 squares satisfy the threshold constraint, avoiding the need to check all possible combinations exhaustively.
Time and Space Complexity
Time Complexity: O(m * n * min(m, n))
The time complexity is determined by:
- Building the prefix sum array takes
O(m * n)time - Binary search runs for
O(log(min(m, n)))iterations - For each binary search iteration, the
checkfunction is called - Inside
check, there are two nested loops iterating(m - k + 1) * (n - k + 1)times, which in the worst case (whenkis small) is approximatelyO(m * n) - Each iteration inside
checkperforms constant time operations using the prefix sum array
Therefore, the overall time complexity is O(m * n * log(min(m, n))). However, since the binary search explores different values of k, and for larger values of k the nested loops in check run fewer times, a tighter bound considering all iterations together gives us O(m * n * min(m, n)).
Space Complexity: O(m * n)
The space complexity is determined by:
- The prefix sum array
swhich has dimensions(m + 1) × (n + 1), requiringO(m * n)space - Other variables like
l,r,mid, and loop counters useO(1)space
Therefore, the overall space complexity is O(m * n).
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template Variant
This is a "find maximum" problem, not "find minimum". The template must be adapted.
Wrong approach (causes infinite loop):
while left < right: mid = (left + right) // 2 if feasible(mid): left = mid # Infinite loop when left = right - 1 else: right = mid - 1
Correct approach using template:
# Reframe: find first infeasible size, answer = first_infeasible - 1 first_true_index = max_possible + 1 while left <= right: mid = (left + right) // 2 if not feasible(mid): # Infeasible is our "true" first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index - 1
2. Off-by-One Errors in Prefix Sum Indexing
Pitfall: Confusing the coordinate system between the original matrix (0-indexed) and the prefix sum array (1-indexed with padding).
Solution: Maintain clear understanding that prefix_sum[i][j] represents the sum of all elements from mat[0][0] to mat[i-1][j-1]. When checking a k×k square starting at mat[i][j], use:
square_sum = (prefix_sum[i + k][j + k] - prefix_sum[i][j + k] - prefix_sum[i + k][j] + prefix_sum[i][j])
3. Incorrect Range Calculation for Square Positions
Pitfall: Using range(m - k) instead of range(m - k + 1) when iterating through possible square positions.
Solution: Always use range(m - k + 1) and range(n - k + 1) to include all valid starting positions.
4. Not Handling Edge Cases
Pitfall: Failing to handle matrices where even a 1×1 square exceeds the threshold.
Solution: By searching for first infeasible and returning first_true_index - 1, we naturally handle this case. If all sizes are infeasible (first_true_index = 1), we return 0.
In a binary min heap, the minimum element can be found in:
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Prefix Sum Given an array of integers arr and a target value find a subarray that sums to the target Return the start and end indices of the subarray Input arr 1 20 3 30 5 4 target 7 Output 1 4 Explanation The subarray 20 3 30 from index 1 inclusive
Coding Interview 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
Want a Structured Path to Master System Design Too? Don’t Miss This!