Given a 2D matrix of integers matrix
and an integer k
, find the maximum sum of a rectangle (submatrix) within the matrix such that its sum is no larger than k
. The rectangle must be made up of contiguous rows and columns. You may assume that at least one rectangle exists with sum no larger than k
.
k
.
Example:
Input: matrix = [[1,0,1],[0,-2,3]], k = 2
Output: 2
Explanation: The rectangle [[0, 1], [-2, 3]] has sum 2 and is the max no larger than k.
At first glance, one might consider checking every possible rectangle in the matrix, calculating its sum, and comparing it with k
. However, this brute-force approach would be extremely slow for larger matrices because the number of rectangles grows rapidly with matrix size.
To optimize, we can draw inspiration from 1D subarray sum problems, particularly the "Maximum Subarray Sum No Larger Than K" problem. By reducing the 2D problem to a series of 1D problems (using row or column boundaries), we can leverage efficient algorithms for the 1D case. The key insight is to fix two boundaries (rows or columns), collapse the matrix between them into a 1D array of sums, and then solve the 1D version for that array.
For the 1D case, finding the max subarray sum no larger than k
can be efficiently solved using prefix sums and a sorted data structure (like a TreeSet in Java, or bisect
in Python).
We solve the problem in two main steps:
k
.
Step-by-step:
left
and right
).rowSums
of size equal to the number of rows, initialized to 0.left
and right
(inclusive), add the column's values to rowSums
.rowSums
that is ≤ k
.k
. This can be done efficiently using binary search.Why this works: By fixing columns and collapsing rows, we transform the problem into a more manageable form. The prefix sum and binary search trick ensures we efficiently find the best subarray sum for each configuration.
Let's use the sample input: matrix = [[1,0,1],[0,-2,3]], k = 2
.
left = 0
. For right = 0
:
rowSums
= [1, 0]right = 1
:
rowSums
= [1+0, 0+(-2)] = [1, -2]right = 2
:
rowSums
= [1+0+1, 0+(-2)+3] = [2, 1]left = 1
and left = 2
. The maximum found remains 2.
Thus, the answer is 2.
Brute-force approach:
- Enumerate all possible rectangles: O(m2 n2) (for m rows, n columns).
- For each rectangle, sum all elements: up to O(mn) per rectangle.
- Total: O(m3 n3), which is impractical for large matrices.
Optimized approach:
- We fix two columns: O(n2) pairs.
- For each pair, we process a 1D array of size m.
- For each, we use prefix sums and maintain a sorted list/set (insertion/search O(log m)).
- So per column pair, it's O(m log m).
- Total: O(n2 m log m).
Space Complexity:
- O(m) for the row sums and prefix set.
By reducing the problem from 2D to 1D and leveraging efficient prefix sum and binary search techniques, we transform a potentially infeasible brute-force approach into an elegant and efficient solution. The key is recognizing that, for each pair of columns, the problem becomes a classic subarray sum problem, which we can solve optimally with the right data structures.
import bisect
class Solution:
def maxSumSubmatrix(self, matrix, k):
if not matrix or not matrix[0]:
return 0
maxSum = float('-inf')
rows, cols = len(matrix), len(matrix[0])
for left in range(cols):
rowSums = [0] * rows
for right in range(left, cols):
for i in range(rows):
rowSums[i] += matrix[i][right]
# Find the max subarray no larger than k
prefixSums = [0]
currSum = 0
currMax = float('-inf')
for sum_ in rowSums:
currSum += sum_
# We want smallest prefix >= currSum - k
idx = bisect.bisect_left(prefixSums, currSum - k)
if idx < len(prefixSums):
currMax = max(currMax, currSum - prefixSums[idx])
bisect.insort(prefixSums, currSum)
maxSum = max(maxSum, currMax)
return maxSum
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
class Solution {
public:
int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
if (matrix.empty() || matrix[0].empty()) return 0;
int maxSum = INT_MIN;
int rows = matrix.size(), cols = matrix[0].size();
for (int left = 0; left < cols; ++left) {
vector<int> rowSums(rows, 0);
for (int right = left; right < cols; ++right) {
for (int i = 0; i < rows; ++i) {
rowSums[i] += matrix[i][right];
}
set<int> prefixSums;
prefixSums.insert(0);
int currSum = 0, currMax = INT_MIN;
for (int sum : rowSums) {
currSum += sum;
auto it = prefixSums.lower_bound(currSum - k);
if (it != prefixSums.end()) {
currMax = max(currMax, currSum - *it);
}
prefixSums.insert(currSum);
}
maxSum = max(maxSum, currMax);
}
}
return maxSum;
}
};
import java.util.TreeSet;
class Solution {
public int maxSumSubmatrix(int[][] matrix, int k) {
if (matrix.length == 0 || matrix[0].length == 0) return 0;
int maxSum = Integer.MIN_VALUE;
int rows = matrix.length, cols = matrix[0].length;
for (int left = 0; left < cols; left++) {
int[] rowSums = new int[rows];
for (int right = left; right < cols; right++) {
for (int i = 0; i < rows; i++) {
rowSums[i] += matrix[i][right];
}
TreeSet<Integer> prefixSums = new TreeSet<>();
prefixSums.add(0);
int currSum = 0, currMax = Integer.MIN_VALUE;
for (int sum : rowSums) {
currSum += sum;
Integer num = prefixSums.ceiling(currSum - k);
if (num != null) {
currMax = Math.max(currMax, currSum - num);
}
prefixSums.add(currSum);
}
maxSum = Math.max(maxSum, currMax);
}
}
return maxSum;
}
}
function maxSumSubmatrix(matrix, k) {
if (!matrix.length || !matrix[0].length) return 0;
let maxSum = -Infinity;
let rows = matrix.length, cols = matrix[0].length;
for (let left = 0; left < cols; left++) {
let rowSums = new Array(rows).fill(0);
for (let right = left; right < cols; right++) {
for (let i = 0; i < rows; i++) {
rowSums[i] += matrix[i][right];
}
// Find the max subarray no larger than k
let prefixSums = [0];
let currSum = 0, currMax = -Infinity;
for (let sum of rowSums) {
currSum += sum;
let idx = lowerBound(prefixSums, currSum - k);
if (idx < prefixSums.length) {
currMax = Math.max(currMax, currSum - prefixSums[idx]);
}
insert(prefixSums, currSum);
}
maxSum = Math.max(maxSum, currMax);
}
}
return maxSum;
}
// Helper: binary search for lower bound
function lowerBound(arr, target) {
let l = 0, r = arr.length;
while (l < r) {
let m = l + ((r - l) >> 1);
if (arr[m] < target) l = m + 1;
else r = m;
}
return l;
}
// Helper: insert in sorted order
function insert(arr, val) {
let idx = lowerBound(arr, val);
arr.splice(idx, 0, val);
}