给定 n n n 个整数(可能为负数)组成的序列 a [ 1 ] , a [ 2 ] , a [ 3 ] , … , a [ n ] a[1],a[2],a[3],…,a[n] a[1],a[2],a[3],…,a[n],求该序列如 a [ i ] + a [ i + 1 ] + … + a [ j ] a[i]+a[i+1]+…+a[j] a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为 0 0 0
输入样例:
6
-2 11 -4 13 -5 -2
输出样例:
20
思路:很明显用 O ( n 2 ) O(n^2) O(n2)可以直接求解,同时也能用 O ( n l o g n ) O(nlogn) O(nlogn)的分治方法来解决,方法是将这个序列对半分,然后最大子段和只会出现在左边序列、右边序列、跨越中间点的序列,可以解决
当然,最好的办法还是动态规划,思路可以顺推,既然只需要遍历一遍,在遍历的过程中我们只需要判断将
a
[
i
]
a[i]
a[i]连到
a
[
i
−
1
]
a[i-1]
a[i−1]上,或者重新从
i
i
i 开始
那么,我们定义一个缓冲值,计算从开始那个点到当前这个点的上一个点 的字段和,若
>
0
>0
>0,则加上当前这个点会得到更大的值,反之就重新设置起点
所以我们甚至不需要用到dp数组,只需要设置一个dp变量,记录到当前点的最大值
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, a[10005], dp[10005];
int ans, k;
int main(){
scanf("%d", &n);
for(int i=0; i<n; i++) scanf("%d", a+i);
for(int i=0; i<n; i++){
if(k>0) k += a[i];
else k=a[i];
if(i) dp[i] = max(dp[i-1], k);
}
printf("%d\n", dp[n-1]);
}
更新一波 当所给的整数均为负数时,仍然求最大字段和
这里用到单调队列来处理,时间复杂度:O(n)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int n, a[maxn], pre[maxn], q[maxn];
int main(){
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d", a+i);
int head = 1, tail = 0, ans = -1e9; q[++tail] = 0;
for(int i=1; i<=n; i++) pre[i] = pre[i-1] + a[i];
for(int i=1; i<=n; i++){
ans = max(ans, pre[i] - pre[q[head]]);
while(head<=tail && pre[i]<=pre[q[tail]]) tail--;
q[++tail] = i;
}
printf("%d\n", ans);
}