363. 矩形区域不超过 K 的最大数值和(利用前缀和转化为最大子序和问题)

题目:

链接:https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/

给定一个非空二维矩阵 matrix 和一个整数 k,找到这个矩阵内部不大于 k 的最大矩形和。

示例:

输入: matrix = [[1,0,1],[0,-2,3]], k = 2
输出: 2
解释: 矩形区域 [[0, 1], [-2, 3]] 的数值和是 2,且 2 是不超过 k 的最大数字(k = 2)。
说明:

矩阵内的矩形区域面积必须大于 0。
如果行数远大于列数,你将如何解答呢?

 

解答:

太难了,看题解做的。

思路来自于最大子序和的题目。

暴力法想一下,如果要遍历所有可能的矩形,几乎是不可能的,而且有大量重复运算。

正确的方法是:利用前缀和。对于每一行的元素,先保存前缀和,即先计算一个二维数组prefix,prefix[i][j]表示:第i行前j个元素的和。

这样计算完了之后,取第i行[x1,x2]的元素和的话,只要计算prefix[i][x2]-prefix[i][x1-1]就可以了。

一个矩形不止左右的边,还有上下的边。所以上下的边我们也用这个方法。对于左右边界固定的矩形(假设为le,ri),矩形内每一行的元素和(其行号为row)为prefix[row][ri]-prefix[row][le-1];

 

竖向从上往下看,把矩形的每一行的元素和想象成一个元素,一共有row-1行,相当于一个一维数组。这时候问题就变成了最大子序和。

 

至于为什么先固定左右边界,上下方向调用最大子序和算法,而不是反过来。是因为题目给了条件:行数远大于列数。

假设行数row,列数col,我们的算法复杂度应该是:O(col^2*row*logrow),因为外层两个for循环是col*col。内部set最大查询复杂度row*log row。

如果先固定上下边界,再左右方向调用最大子序和算法的话:复杂度就是:O(row^2*col*logcol),显然row远大于col的时候,row^2太大了,时间效率不如前者。

代码: 

 1 class Solution {
 2 public:
 3     int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
 4         if(matrix.empty() or matrix[0].empty()){return 0;}
 5         int R=matrix.size(),C=matrix[0].size();
 6         vector<vector<int>> prefix(R+1,vector<int>(C+1,0));
 7         for(int i=1;i<=R;++i){prefix[i][1]=matrix[i-1][0];}
 8         for(int i=1;i<=R;++i){
 9             for(int j=1;j<=C;++j){
10                 prefix[i][j]=prefix[i][j-1]+matrix[i-1][j-1];
11             }
12         }
13         int res=INT_MIN;
14         for(int le=1;le<=C;++le){
15             for(int ri=le;ri<=C;++ri){
16                 //矩形左右边界为[le-1,ri-1],下面考察所有可能上下边界
17                 set<int> area={0};
18                 int pre_area=0;
19                 for(int i=1;i<=R;++i){
20                     int cur_area=prefix[i][ri]-prefix[i][le-1]+pre_area;
21                     auto iter=lower_bound(area.begin(),area.end(),cur_area-k);
22                     if(iter!=area.end()){res=max(res,cur_area-*iter);}
23                     area.insert(cur_area);
24                     pre_area=cur_area;
25                 }
26             }
27         }
28         return res;
29     }
30 };

 


 

 除了变量名基本全一样的代码,别人能跑400ms,我就一脸懵逼了。找了半天,发现了:

人家是set容器直接调用lower_bound成员函数,我调用的是stl库的lower_bound函数。

改为:set自带的lower_bound,空间大家都一样,但时间大大缩短:

 难以置信这两个函数居然效率差这么多,然后去百度,贴一个解释:

 

原因是:stl的lower_bound是二分查找,需要用到随机存取的特性。

但set是红黑树,非线性结构,迭代器是无法随机存取的。

比如这个代码是错的:

1 int main()
2 {
3     set<int> p={1,3,4,25,235,23,234,523};
4     cout<<*(p.begin()+5);
5     getchar();
6     return 0;
7 }

那既然不能随机存取,对set的[le,ri]区间进行二分查找的复杂度也就肯定不止logN了。

所以容器如果自带lower_bound函数,包括其他函数也是一样,如果容器自己实现了相应的函数,应该优先调用。


 

用vector替代set也可以,lower_bound本身就是二分,set内部是红黑树。二者查询指定数据的复杂度都是O(n logn),但这道题来说,vector快一些,因为数据比较大的时候,set建立红黑树比较费时。

用vector的版本:

 

 1 class Solution {
 2 public:
 3     int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
 4         if(matrix.empty() or matrix[0].empty()){return 0;}
 5         int R=matrix.size(),C=matrix[0].size();
 6         vector<vector<int>> prefix(R+1,vector<int>(C+1,0));
 7         for(int i=1;i<=R;++i){prefix[i][1]=matrix[i-1][0];}
 8         for(int i=1;i<=R;++i){
 9             for(int j=1;j<=C;++j){
10                 prefix[i][j]=prefix[i][j-1]+matrix[i-1][j-1];
11             }
12         }
18         int res=INT_MIN;
19         for(int le=1;le<=C;++le){
20             for(int ri=le;ri<=C;++ri){
21                 //矩形左右边界为[le-1,ri-1],下面考察所有可能上下边界
22                 vector<int> area={0};
23                 int pre_area=0;
24                 for(int i=1;i<=R;++i){
25                     int cur_area=prefix[i][ri]-prefix[i][le-1]+pre_area;
27                     auto iter=lower_bound(area.begin(),area.end(),cur_area-k);
28                     if(iter!=area.end()){res=max(res,cur_area-*iter);}
29                     area.insert(lower_bound(area.begin(),area.end(),cur_area),cur_area);
30                     pre_area=cur_area;
31                 }
32             }
33         }
34         return res;
35     }
36 };

 

 

posted @ 2020-02-25 15:05  NeoZy  阅读(426)  评论(0)    收藏  举报