問題描述:
給定一個數組,從中尋找最大子數組(最大子數組:子數組內元素之和最大)。顯然此問題僅在數組中包含正負值下才有效,否則,最大子數組是其本身。
問題分析:
設原數組爲A[0……len-1],子數組中任意元素爲B[i]。則i-1,i-2,……0,i+1,i+2……len-1,皆有可能是子數組內元素。因此我們可以將數組劃分爲一個個單一元素,然後再通過合併確定子數組的邊界情況。
問題求解:
暴力求解:
遍歷數組的所有子集情況,最終找到的答案毋庸置疑,但是性能堪憂(n^2)。
分治求解:
先來分析下子數組的位置情況。
首先將數組一分爲二(中間位置爲mid),則子數組位置不外乎以下三種情況:
- 在mid左側
- 在mid右側
- 橫跨mid
毫無疑問,此性質在每次數組二分之後(劃分爲原數組的一半)仍滿足。
左右兩側情況相較容易處理,但橫跨mid情況比較特殊(無法分解求解)。因此我們可以單獨處理此種情況。既然左右兩側均是子數組的子集,則我們可以每次從mid開始向左和右遍歷。以向左爲例:從mid開始遍歷,設此時最大子數組爲A[mid]。則最大子數組要麼爲A[mid],要麼爲A[mid, mid-1,……mid-i](i=1, 2, ……,但i不能小於左側邊界)。向右遍歷與此情況相同,但避免mid被重複計算,則應從mid+1開始遍歷。
代碼示例:
#include<iostream>
using namespace std;
#define INT -1e7
struct Data{
int lo, hi;
int sum;
};
Data maxCrossingSubarray(int *A, int lo, int mi, int hi){
int leftSum = INT, rightSum = INT;
int left, right;
int sum = 0;
for(int i = mi; i >= lo; i--){
sum += A[i];
if(sum > leftSum){
leftSum = sum;
left = i;
}
}
sum = 0;
for(int i = mi+1; i <= hi; i++){
sum += A[i];
if(sum > rightSum){
rightSum = sum;
right = i;
}
}
Data temp;
temp.lo = left;
temp.hi = right;
temp.sum = leftSum + rightSum;
return temp;
}
Data maxSubarray(int *A, int lo, int hi){
Data temp, left, right, cross;
if(lo == hi){
temp.hi = temp.lo = lo;
temp.sum = A[lo];
return temp;
}
int mi = (lo + hi) / 2;
left = maxSubarray(A, lo, mi);
right = maxSubarray(A, mi+1, hi);
cross = maxCrossingSubarray(A, lo, mi, hi);
if(left.sum >= right.sum && left.sum >= cross.sum)
return left;
else if(right.sum >= left.sum && right.sum >= cross.sum)
return right;
else return cross;
}
int main(){
int n;
cin >> n;
int A[n];
for(int i = 0; i < n; i++){
cin >> A[i];
}
Data temp = maxSubarray(A, 0, n-1);
cout << temp.lo << " " << temp.hi << " " << temp.sum;
return 0;
}
時間雜度分析:
maxSubarray()函數遞歸調用同二分算法相同,最多經過logn此遞歸到達遞歸基,並且有n個元素需要合併,因此時間複雜度爲O(nlogn)。
線性複雜度求解:
若A[0……i]最大子數組爲B,則A[0……i+1]的最大子數組要麼爲B要麼爲A[0……i+1]內的任意一段。有了這個思路則可以自左至右遍歷整個數組,每掃描一個元素則按上述性質比較。
代碼示例:
#include<iostream>
using namespace std;
struct Data{
int lo, hi;
int sum;
};
Data initData(Data t, int n){
t.sum = n;
t.lo = 0;
t.hi = 0;
return t;
}
Data maxSubarray(int *A, int len){
Data t, s;
t = initData(t, A[0]);
s = initData(s, A[0]);
for(int i = 1; i < len; i++){
s.sum += A[i];
if(s.sum >= 0){
s.hi = i;
if(s.sum > t.sum){
t.sum = s.sum;
t.hi = s.hi;
t.lo = s.lo;
}
} else{
s.sum = 0;
s.lo = i+1;
}
}
return t;
}
int main(){
int n[] = {67, 0, 24, -58, -64, 45, 27, 91};
Data temp = maxSubarray(n, 8);
cout << temp.lo << " " << temp.hi << " " << temp.sum;
return 0;
}
此種算法只需要遍歷一遍整個數組便可以求解問題,因此時間複雜度爲O(n)。