您的位置:首页 > 其它

[算法]最大子段和问题

2016-03-13 14:56 253 查看
给定长度为n的整数序列,a[1…n], 求[1,n]某个子区间[i , j]使得a[i]+…+a[j]和最大.或者求出最大的这个和.例如(-2,11,-4,13,-5,2)的最大子段和为20,所求子区间为[2,4].

解题思路

穷举

通过三重循环遍历所有的子段可能,取得最大和时的子段下标,简单但低效,并且当序列范围太大时,可能会超时。

int begin = 0;
int end = 0;
int max_sum = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int sum = 0;
for (int k = i; k <= j; k++) {
sum += array[k];
}
if (sum > max_sum) {
max_sum = sum;
begin = i;
end = j;
}
}
}


但是我们可以通过优化O(n^3)的穷举代码,消除第三个循环,使它变成O(n^2)复杂度。

int begin = 0;
int end = 0;
int max_sum = 0;
for (int i = 0; i < n; i++) {
int sum = 0;
for (int j = i; j < n; j++) {
sum += array[j];
if (sum > max_sum) {
max_sum = sum;
begin = i;
end = j;
}
}
}


分治

考虑到子段区间[begin,end]可能存在的情况有三种:

位于[0,n/2]区域内

位于[n/2+1,n-1]区域内

起点位于[0,n/2]区域内,终点位于[n/2+1,n-1]区域内

那么最后的结果只能是上述三种结果中的最大值,所以可以利用分治的思想,前两种情况使用递归方法,第三种情况使用穷举法求出。

int maxSub(int* n, int left, int right) {
if (left == right)
return n[left]>0?n[left]:0;
int mid = (left+right)/2;
int leftSub = maxSub(n,left,mid);
int rightSub = maxSub(n,mid+1,right);

int sum = 0;
int leftMax = 0;
//向左边扩散
for (int i = mid; i >= left; i--) {
sum  += n[i];
if (sum > leftMax) {
leftMax = sum;
}
}
sum = 0;
int rightMax = 0;
//向右边扩散
for (int i = mid+1; i <= right; i++) {
sum += n[i];
if (sum > rightMax) {
rightMax = sum;
}
}
int ans = leftMax + rightMax;
ans = max(max(ans,leftSub),max(ans,rightSub));
return ans;
}


这种分治的方法复杂度是O(nlog n),可以满足大多数样例。

- 动态规划

这里引用一段网上对该问题的动态规划的分析:



int* b = new int[n+1];
memset(b,0,n+1);
for (int i = 1; i < n+1; i++) {
if (b[i-1] > 0) {
b[i] = b[i-1]+a[i];
} else {
b[i] = a[i];
}
}
return max(b
,0);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: