Maximum Side Length of a Square with Sum Less than or Equal to Threshold
MedPrompt
Given a m x n matrix mat and an integer threshold, return the maximum side-length of a square with a sum less than or equal to threshold or return 0 if there is no such square.
Example 1:
Input: mat = [[1,1,3,2,4,3,2],[1,1,3,2,4,3,2],[1,1,3,2,4,3,2]], threshold = 4
Output: 2
Explanation: The maximum side length of square with sum less than 4 is 2 as shown.Example 2:
Input: mat = [[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2]], threshold = 1
Output: 0
Constraints:
m == mat.lengthn == mat[i].length1 <= m, n <= 3000 <= mat[i][j] <= 1040 <= threshold <= 105
Approaches
2 approaches with complexity analysis and trade-offs.
This approach improves upon a naive brute-force method by first pre-calculating a 2D prefix sum array (also known as a summed-area table). This allows the sum of any rectangular submatrix to be calculated in constant time. The main algorithm then iterates through all possible square side lengths, from largest to smallest. For each side length, it checks every possible square of that size. The first one found that satisfies the sum condition corresponds to the maximum side length.
Algorithm
- Preprocessing: Prefix Sums
- Create a 2D prefix sum array,
prefix, of size(m+1) x (n+1)to store cumulative sums. - Iterate through the input matrix
matto populate theprefixarray. The sum of the rectangle from(0,0)to(i-1,j-1)is calculated as:prefix[i][j] = mat[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1].
- Create a 2D prefix sum array,
- Main Logic: Iteration
- Iterate through all possible side lengths
kin descending order, frommin(m, n)down to 1. - For each
k, iterate through all possible top-left corners(r, c)of ak x ksquare. - Calculate the sum of the current square in
O(1)time using theprefixarray:sum = prefix[r+k][c+k] - prefix[r][c+k] - prefix[r+k][c] + prefix[r][c]. - If
sum <= threshold, we have found a valid square. Since we are iteratingkfrom largest to smallest, thiskmust be the maximum possible side length. Returnkimmediately. - If the loops complete without finding any valid square, it means no such square exists. Return 0.
- Iterate through all possible side lengths
Walkthrough
The core of this method is the optimization of the sum calculation. Instead of summing up k*k elements for every square, we pre-process the matrix in O(m*n) time to build a prefix sum array. prefix[i+1][j+1] stores the sum of all elements in the rectangle from mat[0][0] to mat[i][j].
With the prefix array, the sum of a square with top-left corner (r, c) and side length k can be found in O(1) using an inclusion-exclusion principle. The algorithm then searches for the answer by checking side lengths in a linear scan, from min(m, n) down to 1. The first side length k for which a valid square is found is guaranteed to be the maximum, so we can return it immediately. If the scan completes, no valid square exists, and we return 0.
class Solution { public int maxSideLength(int[][] mat, int threshold) { int m = mat.length; int n = mat[0].length; int[][] prefix = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { prefix[i][j] = mat[i - 1][j - 1] + prefix[i - 1][j] + prefix[i][j - 1] - prefix[i - 1][j - 1]; } } for (int k = Math.min(m, n); k >= 1; k--) { for (int r = 0; r <= m - k; r++) { for (int c = 0; c <= n - k; c++) { // In the prefix sum array, the indices are 1-based. // A square from (r, c) to (r+k-1, c+k-1) in mat corresponds to // the rectangle between (r, c) and (r+k, c+k) in the prefix sum grid. int sum = prefix[r + k][c + k] - prefix[r][c + k] - prefix[r + k][c] + prefix[r][c]; if (sum <= threshold) { return k; // Found the largest possible k } } } } return 0; // No square found }}Complexity
Time
O(m * n * min(m, n)). The prefix sum calculation is `O(m*n)`. The main part involves three nested loops for side length `k`, row `r`, and column `c`, resulting in a cubic time complexity relative to the matrix dimensions.
Space
O(m * n) to store the 2D prefix sum array.
Trade-offs
Pros
Conceptually simpler to understand and implement than a binary search approach.
Significantly more efficient than a naive brute-force solution that doesn't use prefix sums.
Cons
The time complexity of
O(m * n * min(m, n))can be too slow if the matrix dimensions are very large, although it may pass for the given constraints.It performs many checks that could be avoided with a more targeted search strategy like binary search.
This approach optimizes the search for the maximum side length by using binary search. The key observation is that the problem has a monotonic property: if a square of side length k with a sum less than or equal to the threshold exists, then a valid square of any side length smaller than k must also exist (since all matrix elements are non-negative). This allows us to efficiently search for the maximum valid side length in the range [0, min(m, n)].
Algorithm
- Preprocessing: Prefix Sums
- As in the previous approach, create and populate a
(m+1) x (n+1)2D prefix sum array inO(m*n)time.
- As in the previous approach, create and populate a
- Helper Function:
hasValidSquare(k)- Create a function that takes a side length
kand checks if anyk x ksquare has a sum less than or equal tothreshold. - This function iterates through all possible
k x ksquares, calculates their sum inO(1)using the prefix sum array, and returnstrueif a valid one is found. Otherwise, it returnsfalseafter checking all possibilities. This check takesO(m*n)time.
- Create a function that takes a side length
- Binary Search
- Initialize search boundaries
low = 0,high = min(m, n), and a result variableans = 0. - While
low <= high: a. Calculate the middle side lengthmid = low + (high - low) / 2. b. CallhasValidSquare(mid). c. If it returnstrue,midis a possible answer. We try for a larger square by settingans = midandlow = mid + 1. d. If it returnsfalse,midis too large. We search in the lower half by settinghigh = mid - 1. - Return
ans.
- Initialize search boundaries
Walkthrough
The foundation of this method is to transform the problem from finding a maximum value to a series of decision problems. Instead of asking "What is the max length?", we ask "Does a square of length k exist?". This yes/no question can be answered efficiently.
First, we pre-compute the 2D prefix sum array in O(m*n) time, which allows for O(1) sum queries for any submatrix.
Then, we perform a binary search on the possible side lengths k. For each mid value in our binary search, we have a helper function, hasValidSquare(mid), which determines if there's any square of size mid x mid with a sum <= threshold. This check takes O(m*n) time.
If hasValidSquare(mid) is true, we know mid is a potential answer, and we try to find an even larger one by searching in the upper half (low = mid + 1). If it's false, mid is too large, and we must search for a smaller side length in the lower half (high = mid - 1). The largest mid for which the check was successful is stored and eventually returned as the answer.
class Solution { public int maxSideLength(int[][] mat, int threshold) { int m = mat.length; int n = mat[0].length; int[][] prefix = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { prefix[i][j] = mat[i - 1][j - 1] + prefix[i - 1][j] + prefix[i][j - 1] - prefix[i - 1][j - 1]; } } int low = 0, high = Math.min(m, n), ans = 0; while (low <= high) { int mid = low + (high - low) / 2; if (mid == 0) { low = mid + 1; continue; } if (hasValidSquare(mid, m, n, prefix, threshold)) { ans = mid; // mid is a possible answer, try for a larger one low = mid + 1; } else { high = mid - 1; // mid is too large, try a smaller one } } return ans; } private boolean hasValidSquare(int k, int m, int n, int[][] prefix, int threshold) { for (int r = 0; r <= m - k; r++) { for (int c = 0; c <= n - k; c++) { int sum = prefix[r + k][c + k] - prefix[r][c + k] - prefix[r + k][c] + prefix[r][c]; if (sum <= threshold) { return true; } } } return false; }}Complexity
Time
O(m * n * log(min(m, n))). The `log(min(m, n))` factor comes from the binary search. For each of the `log` steps, we perform a check that takes `O(m*n)` time. The initial prefix sum calculation takes `O(m*n)`.
Space
O(m * n) to store the 2D prefix sum array.
Trade-offs
Pros
Highly efficient, with a time complexity that is significantly better than a linear scan.
It is the optimal approach for the given constraints.
Leverages a standard and powerful algorithmic pattern (binary search on the answer).
Cons
Slightly more complex to implement due to the binary search logic and the need for a helper function.
Solutions
Solution
class Solution {private int m;private int n;private int threshold;private int[][] s;public int maxSideLength(int[][] mat, int threshold) { m = mat.length; n = mat[0].length; this.threshold = threshold; s = new int[m + 1][n + 1]; for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + mat[i - 1][j - 1]; } } int l = 0, r = Math.min(m, n); while (l < r) { int mid = (l + r + 1) >> 1; if (check(mid)) { l = mid; } else { r = mid - 1; } } return l; }private boolean check(int k) { for (int i = 0; i < m - k + 1; ++i) { for (int j = 0; j < n - k + 1; ++j) { if (s[i + k][j + k] - s[i][j + k] - s[i + k][j] + s[i][j] <= threshold) { return true; } } } return false; }}class Solution {public: int maxSideLength(vector<vector<int>> &mat, int threshold) { int m = mat.size(), n = mat[0].size(); int s[m + 1][n + 1]; memset(s, 0, sizeof(s)); for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + mat[i - 1][j - 1]; } } auto check = [&](int k) { for (int i = 0; i < m - k + 1; ++i) { for (int j = 0; j < n - k + 1; ++j) { if (s[i + k][j + k] - s[i][j + k] - s[i + k][j] + s[i][j] <= threshold) { return true; } } } return false; }; int l = 0, r = min(m, n); while (l < r) { int mid = (l + r + 1) >> 1; if (check(mid)) { l = mid; } else { r = mid - 1; } } return l; }};class Solution: def maxSideLength(self, mat: List[List[int]], threshold: int) -> int: 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 m, n = len(mat), len(mat[0]) 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 l, r = 0, min(m, n) while l < r: mid = (l + r + 1) >> 1 if check(mid): l = mid else: r = mid - 1 return lVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.