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 };



浙公网安备 33010602011771号