Max Sum of Rectangle No Larger Than K (Hard)
update Mar 15, 2018 23:03
Given a non-empty 2D matrix matrix and an integer k, find the max sum of a rectangle in the matrix such that its sum is no larger than k.
Example:
Given matrix = [
[1, 0, 1],
[0, -2, 3]
]
k = 2
The answer is 2. Because the sum of rectangle [[0, 1], [-2, 3]]
is 2 and 2 is the max number no larger than k (k = 2)
.
Note:
- The rectangle inside the matrix must have an area
> 0
. - What if the number of rows is much larger than the number of columns?
Basic Idea:
这道题的难度主要在于整合了两道题在一起。首先,要知道如何在一个一维数组中求 subarray 的不超过 k 的最大和,进而将这种解法推广到二维情况。
对于这道题,我们可以 O(N^2)
时间枚举所有的左右边界对,而对于已经确定左右边界的中间部分,我们可以通过预处理生成每个row的 refix sum 数组,实现 O(1)
时间求左右边界之间某一row的和,于是此时这个问题就变成了求一个一维数组的最大不超过 k 的 subarray 的和,总的时间复杂度也就是这一步的时间乘以之前枚举左右边界的次数 O(N^2)
。
下面着重说一下一维数组求最大不超过 k 的 subarray sum 的解法。解法有两种,都要利用 prefix sum 的技巧:
1. 利用 TreeMap(BST):
先求出 prefix sum 数组,然后逐个将 prefix sum 数组中的元素sums[i]
加入 BST,每次加入之前检查BST中之前的使得sums[i] - prev < k
的最小 prev,并用sums[i] - prev
和全局最大值对比,更新全局最大值。每次操作 BST 耗时O(logM)
,共需要操作 N 次,所以时间复杂度O(MlogM)
;
2. 利用排序,双指针:
先求出 prefix sum 数组 sums,然后将 sums 排序,利用双指针法从左右两端点向内逼近,沿途更新全局最大差值,时间复杂度为 O(MlogM + M) = O(MlogM);
特别注意,2 这种方法是行不通的。这种从两端向中间逼近的双指针算法只适合处理需要给定和的大小,而不能用来处理差的大小。因为事实上当right-left太小时,无论左移right还是右移left都会令其变得更小。相反,右移left和以让left+right变大,左移right则可以让left+right变小。
最后,我们可以知道总的时间复杂度为 O(N^2 * MlogM)
;
对于Follow Up:
如果rows远多于cols,我们就枚举cols而不是rows(也就是之前所说的枚举所有左右边界),其目的就是让时间复杂度
O(N^2 * MlogM)
中的 N 是更小的那个边长就可以。
Java Code (只能用BST):
class Solution {
public int maxSumSubmatrix(int[][] matrix, int k) {
int R = matrix.length, C = matrix[0].length;
// 先求所有row的 prefix sum
int[][] sums = new int[R][C];
for (int r = 0; r < R; ++r) {
for (int c = 0; c < C; ++c) {
if (c == 0) {
sums[r][c] = matrix[r][c];
} else {
sums[r][c] = matrix[r][c] + sums[r][c - 1];
}
}
}
// 枚举所有左右边界,对其内部部分视为一个纵向一维数组,利用BST求其max subarray sum 不大于 k
int globalMaxSum = Integer.MIN_VALUE;
for (int left = 0; left < C; ++left) {
for (int right = left; right < C; ++right) {
// 开始处理一维数组的子问题
TreeSet<Integer> bst = new TreeSet<>();
bst.add(0); // add 0, 这样就可以考虑到只有一个元素的情况
int currPrefixSum = 0;
for (int r = 0; r < R; ++r) {
int currRowSum = sums[r][right] - sums[r][left] + matrix[r][left]; // 注意这里的技巧
currPrefixSum += currRowSum;
Integer prev = bst.ceiling(currPrefixSum - k); // 找到之前出现过的使得 currPrefixSum-prev<k 的最小prev
if (prev != null) {
globalMaxSum = Math.max(globalMaxSum, currPrefixSum - prev);
}
bst.add(currPrefixSum);
}
}
}
return globalMaxSum;
}
}