问题描述:
给定一个数组,从中寻找最大子数组(最大子数组:子数组内元素之和最大)。显然此问题仅在数组中包含正负值下才有效,否则,最大子数组是其本身。
问题分析:
设原数组为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)。