一个矩阵的所有子矩阵最大和问题、Kadane算法
来源:互联网 发布:房地产网络推广 编辑:程序博客网 时间:2024/06/04 18:07
Preface
今天早上刷微博,看到LeetCode中国微博发了这样一条状态:
已经好久没做题练练手了,于是想试试。LeetCode上,该题目的地址为:https://leetcode.com/problems/max-sum-of-sub-matrix-no-larger-than-k/
Analysis
想了一上午,也没想出什么头绪。后来我看 LeetCode 上有不少人已经做出提交了。并且,在discuss页面里,有人公布了详细的解释与代码。
我看了一下,他这个解法是基于Kadane Algorithm了。于是,先得学习一下什么是Kadane Algorithm。
Kadane Algorithm
Kadane Algorithm 用于解决对一列数组中,求其中子序列的和最大的值。Kadane 的代码很多,各种语言的也都有,我下面摘取这个网站上的C++
代码,理解分析一下:
#include <iostream>#include <climits>using namespace std;#define MAX(X, Y) (X > Y) ? X : Y#define POS(X) (X > 0) ? X : 0int kadane(int* row, int len){ int x; //拿数组的第一个元素出来,若其大于0,则另sum = row[0] //若其小于或等于0,则令sum = 0, int sum = POS(row[0]); int maxSum = INT_MIN; //INT_MIN是<climits>文件定义的,代表int类型最小值:-2147483648 for (x = 0; x < len; ++x) { //Kadane 算法的核心部分 //maxSum用于记录最大的子序列和,并每一次与sum进行比较,若当sum比之前的maxSum要大,则将现在的sum值赋予maxSum //sum每加一个值,跟0进行一次比较,若加完row[x]都小于0了,那么就直接将sum置为0,接着开始一个新的子序列,并进行求和 maxSum = MAX(sum, maxSum); sum = POS(sum + row[x]); } return maxSum;}int main(){ int N; cout << "Enter the array length: "; cin >> N; int arr[N]; cout << "Enter the array: "; for (int i = 0; i < N; i++) { cin >> arr[i]; } cout << "The Max Sum is: "<<kadane(arr, N) << endl; return 0;}
2D Kadane Algorithm
由于我们这一题是二维矩阵,并不是一维数组。因此,要将 kadane 算法扩展到2维上。同样作者也推荐了一个视频,是位印度哥们,讲解的非常好。视频在 YouTube 上,地址:https://www.youtube.com/watch?v=yCQN096CwWM,保证听几遍就懂。
下面我就他讲解的,用 Excel 表格展示这个二维 kadane 算法的过程。
如图下面所示的矩阵,黄色黄色部分,
1. 变量
2. 变量
3. 右边浅绿色,与矩阵的
4. 变量
5. 变量
6. 变量
7. 变量
8. 变量
9. 变量
注意:如果
第一次遍历,
第二次遍历, 此时将
很容易看出,最大值为9,所以
第三次遍历:
第四次遍历:
第五次遍历:
第六次遍历:
第七次遍历:
第八次遍历:
第九次遍历:
第十次遍历:
第十一次遍历:
第十二次遍历:
第十三次遍历:
第十四次遍历:
第十五次遍历:
经过十五次的遍历后,我们终于找到了这个矩阵,就是上图中红色区域部分。这个大矩阵(
这就是2D kadane算法的过程。这个算法的空间复杂度为:
Find the max sum no more than K
解决了如何寻找子矩阵的最大和问题,现在题目中还有一个限制。就是这个和不能大于给定的
直接看大神给的代码吧:
int best_cumulative_sum(int ar[], int N, int K){ set<int> cumset; cumset.insert(0); int best = 0, cum = 0; for(int i = 0; i < N; i++) { cum += ar[i]; //upper_bound(), 返回指向容器中第一个值在给定搜索值之后的元素的迭代器 set<int>::iterator sit = cumset.upper_bound(cum - K); if(sit != cumset.end()) best = max(best, cum - *sit); cumset.insert(cum); } return best;}
First thing to note is that sum of subarray
(i,j] is just the sum of the firstj elements less the sum of the firsti elements. Store these cumulative sums in the array cum. Then the problem reduces to findingi,j such thati<j andcum[j]−cum[i] is as close tok but lower than it.
所谓子序列(i,j] 元素之和,就是这个序列的j 元素之和减去(less)这个序列的前i 个元素之和。所以问题转化为找到这样的i,j(i<j) ,使得cum[j]−cum[i] 尽可能的大,接近给定的限制值k ,但是小于这个k 。To solve this, scan from left to right. Put the
cum[i] values that you have encountered till now into a set. When you are processingcum[j] what you need to retrieve from the set is the smallest number in the set such which is bigger thancum[j]−k . This lookup can be done inOlogn using upper_bound. Hence the overall complexity isO(nlog(n)) .
从左到右的遍历这个序列。将这个序列的前i(i<N) (i 从0 开始) 号元素之和存放到一个 set 中(注意:set 是按小到大顺序对元素排序的),当你处理前j 个元素之和cum[j] 时,你需要在cum[ ] 序列中,找到最小的这i,i<j ,它的前i 个序列之和为cum[i] :
cum[j]−cum[i]<K ⇒cum[j]−K<cum[i]
这就是代码中set<int>::iterator sit = cumset.upper_bound(cum - K)
,这一行的由来。
有些难理解,举个例子。这里,一开始的数组值为:ar[] = [-4 6 -3 8 -9]
,给定的N = 5
, K = 12
.
这个函数的变量变化见下表:
Show the Code
解决了这个问题中的两个关键问题,下面就是写这个二维矩阵子矩阵之和最大问题的代码了。下面是作者给出的代码:
int maxSumSubmatrix(vector<vector<int> >& matrix, int k) { //判断矩阵是否为空矩阵 if (matrix.empty()) return 0; int row = matrix.size(), col = matrix[0].size(), res = INT_MIN; //就像前面演示的那样,l代表变量L,r代表变量R for (int l = 0; l < col; ++l) { //之前演示的,临时存储区,与矩阵的row相同,单列;同时,开始值赋予0 vector<int> sums(row, 0); //r从每一次的l处开始:r = l,直到最右边col:r < col for (int r = l; r < col; ++r) { for (int i = 0; i < row; ++i) { //对当前列,加上之前的列(从l开始,到当前的r列),进行列相加。 //即,当r向右移动时,每一行保持之前的值存在sum[i](i: [0,row)), //接着,再加上新的列(r)上同一行新出现的元素 sums[i] = sums[i] + matrix[i][r]; } // 对当前的临时存储区的列,求其最大子序列 // 这部分的代码就是上面Quora上的代码 // Find the max subarray no more than K set<int> accuSet; accuSet.insert(0); int curSum = 0, curMax = INT_MIN; for (int sum : sums) { curSum = curSum + sum; set<int>::iterator it = accuSet.lower_bound(curSum - k); if (it != accuSet.end()) curMax = std::max(curMax, curSum - *it); accuSet.insert(curSum); } // 拿当前的最大子矩阵之和与之前求得的最大子矩阵之和做比较,保留最大值 res = std::max(res, curMax); } } return res;}
这段代码的精华之处太多,应多细细体会。
至此,这一题解决。
Reference
- https://leetcode.com/discuss/109749/accepted-c-codes-with-explanation-and-references
- https://www.youtube.com/watch?v=yCQN096CwWM
- https://www.youtube.com/watch?v=86CQq3pKSUw
- https://www.quora.com/Given-an-array-of-integers-A-and-an-integer-k-find-a-subarray-that-contains-the-largest-sum-subject-to-a-constraint-that-the-sum-is-less-than-k
- http://www.hawstein.com/posts/20.12.html
- http://kubicode.me/2015/06/23/Algorithm/Max-Sum-in-SubMatrix/
注:参考5、6是我觉得写的不错的博客,推荐作为扩展阅读
- 一个矩阵的所有子矩阵最大和问题、Kadane算法
- 从一道easy leetcode问题,谈谈最大子列和的Kadane算法
- 求一个矩阵最大子矩阵的算法模板
- 【算法】子数组的最大累加和/子矩阵的最大累加和问题
- 子矩阵的最大累加和问题
- 求一个矩阵的最大子矩阵
- 数组与矩阵---子矩阵的最大累加和问题
- 最大子矩阵和问题
- 最大子矩阵和问题
- 最大子矩阵和问题
- 最大子矩阵和问题
- 最大子矩阵和问题
- 最大子矩阵和问题
- 最大子矩阵和问题、
- 最大子矩阵的和
- 最大子矩阵的和
- 最大子矩阵的和
- Leetcode121.+Leetcode53. Kadane算法解决最大子数组问题
- 为ZendStudio增加CodeIgniter的智能提\
- Java多线程编程总结(转载)
- 第二章 CSS工作原理(ID属性和类选择符)
- 数据库学习及心得体会
- c#处理json数据
- 一个矩阵的所有子矩阵最大和问题、Kadane算法
- Swift开篇013->析构过程
- HTML常用标签
- PDO属性、方法
- 提交刷新页面
- 15.4节练习
- git之 .gitignore的配置和使用
- java压缩文件
- 对象类型转换