题目
给定一个非空二维矩阵 matrix 和一个整数 k,找到这个矩阵内部不大于 k 的最大矩形和。
示例:
输入: matrix = [[1,0,1],[0,-2,3]], k = 2
输出: 2
解释: 矩形区域 [[0, 1], [-2, 3]] 的数值和是 2,且 2 是不超过 k 的最大数字(k = 2)。
说明:
矩阵内的矩形区域面积必须大于 0。
如果行数远大于列数,你将如何解答呢?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k
解题
思路(固定左右边界求矩形最大和将其转化为一维数组最大子序和问题)
对matrix = [[5,-4,-3,4],[-3,-4,4,5],[5,1,5,-4]]的矩阵来说,如下图,要选择其中子矩阵的小于K的最大矩阵和。最直观的思路就是枚举所有的(左边界,右边界)和(上边界,下边界),然后求这四个边界内的元素的和,和的求法可以是每一行相加也可以是每一列相加。
暴力法来说,循环一般写成先枚举一对边界的组合,再枚举另一对边界的组合。因为枚举一对边界就是两层循环,这样下来是四层循环。我们要想办法简化问题。
我们可以固定一对边界(因为行数远大于列数,所以先枚举固定左右边界),然后不用枚举另一种边界的组合可以通过这样的方式来完成:固定左右边界后,上下边界组合下的矩阵的和我们可以通过该左右边界下每一行的和的方式来计算。
所以我们枚举不同的左右边界组合,对每一种左右边界来说,求该边界条件下其每一行的和用rowsum[row.length]存储。这样枚举上下边界并求该上下左右边界构成的矩形最大和的方式就变成了求rowsum[]数组的最大子序和的问题。这样既完成了上下边界的枚举,又相当于将二维数组压缩成了一维数组来求解。如下图示例,当左右边界为[0,2]时,rowsum[] = {-2,-3,11}。问题就变成求解rowsum数组的小于K的最大子序和的求解了。如下给出了几种解法。
此时的矩形和求解方法例如下面这种直接求法:
rowsum[]的小于K的最大子序和解法
暴力求法
枚举所有情况,对连续的子数组进行依次累加维护小于K的最大值即可。
class Solution {
public int maxSumSubmatrix(int[][] matrix, int k) {
//固定左右边界,求出该左右边界内每行的和用rowsum[]存储,只考虑哪两个行之间的矩形和最大,就变成了求rowsum中最大子序和的问题
//1、划定左右边界,求每行和;2、找不超过K的两行的组合(即对应矩阵)的最大值
//为什么用列划分,因为行数远超于列数
int row = matrix.length;//行数
//非空矩阵不用判断特殊情况
int col = matrix[0].length;//列树
int res = Integer.MIN_VALUE;//初始化结果为最小值
for(int l = 0; l < col;l++){
//l为左边界
int[] rowsum = new int[row];//每一行的和的数组
for(int r = l; r < col; r++){
//r为有边界
for(int i = 0; i < row; i++){
//求当前左右边界[l,r]时每行的和的前缀和,以便求不同行之间的矩形和
rowsum[i] += matrix[i][r];
}
//求[l,r]边界下矩阵不超过k的最大矩形和
res = Math.max(res,lrmax(rowsum,k));
if(res == k) return k;//尽量减少运算
}
}
return res;
}
//求[l,r]边界下矩阵不超过k的最大矩形和,变成rowsum数组的小于K的最大子序和问题
public int lrmax(int[] rowsum, int k){
//暴力法 O(row^2)
int max = Integer.MIN_VALUE;
for(int i = 0; i < rowsum.length; i++){
int sum = 0;
for(int j = i; j < rowsum.length;j++){
//i到j行之间的和就是依次叠加
sum += rowsum[j];
if(sum<=k && sum>max) max = sum;
if(max == k) return k;
}
}
return max;
}
}
前缀和求法
在小于K的最大子序和的求解上,采用前缀和,前缀和详解具体看leetcode【前缀和】 | 560. 和为K的子数组
class Solution {
public int maxSumSubmatrix(int[][] matrix, int k) {
//固定左右边界,求出该左右边界内每行的和用rowsum[]存储,只考虑哪两个行之间的矩形和最大,就变成了求rowsum中最大子序和的问题,用前缀和求(参考"leetcode 560. 和为K的子数组")
//1、划定左右边界,求每行和;2、找不超过K的两行的组合(即对应矩阵)的最大值
//为什么用列划分,因为行数远超于列数
int row = matrix.length;//行数
//非空矩阵不用判断特殊情况
int col = matrix[0].length;//列树
int res = Integer.MIN_VALUE;//初始化结果为最小值
for(int l =