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 : 0
int 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是我覺得寫的不錯的博客,推薦作爲擴展閱讀