算法与数据结构---4.9、最大子段和-dp空间优化

算法与数据结构---4.9、最大子段和-dp空间优化

一、总结

一句话总结:

在最大字段和的动态规划的解法的代码中,我们发现用来做动态规划的数组f在代码中只用到了f[i]和f[i-1],所以我们可以用只有两个元素的滚动数组来优化f数组
/*

我们来看一眼代码:
代码中用到a数组位置除了a[1]这个固定的,剩下的就是a[i]
所以a数组可以被一个变量代替。


然后再来看一眼f数组
f数组:全程我们只用到了f[i]元素和f[i-1]元素
是不是闻到了滚动数组的气息
最终我们可以得出空间优化版

*/
#include <iostream>
#include <algorithm>
using namespace std;
int f[2]={0};
int main(){
    int n;
    cin>>n;
    cin>>f[1];
    //1、确定动态规划初始条件:f[1]=a[1]
    int maxx=f[1];
    for(int i=2;i<=n;i++){
        int x;
        cin>>x;
        //2、动态规划操作:f[i]=max(f[i-1]+a[i],a[i]) (2<=i<=n)
        f[i%2]=max(f[!(i%2)]+x,x);
        //3、求ans:Answer=max{f[i]|1<=i<=n}
        maxx=max(f[i%2],maxx);
    }
    cout<<maxx<<endl;
    return 0;
}

二、最大子段和

博客对应课程的视频位置:4.9、最大子段和-dp空间优化
https://www.fanrenyi.com/video/27/271

1、题目描述

最大子段和(最大连续子序列的和)

题目描述
给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。

输入格式
第一行是一个整数,表示序列的长度 n。
第二行有 n 个整数,第 i 个整数表示序列的第 i 个数字 ai

输出格式
输出一行一个整数表示答案。

输入输出样例
输入
7
2 -4 3 -1 2 -4 3
输出
4

说明/提示
样例解释
选取 [3,5] 子段{3,−1,2}最大,其和为 4。

数据规模与约定
对于40%的数据,保证n<=2×10^3
对于100%的数据,保证1<=n<=2×10^5, -10^4<=a[i]<=10^4

题目提交位置:
P1115 最大子段和 - 洛谷
https://www.luogu.com.cn/problem/P1115

2、枚举解法

 1 /*
 2 枚举法
 3 
 4 分析:
 5 我们可以直接按照题目的要求来枚举就好了
 6 
 7 题目的要求是要 求a[1]-a[n]中连续非空的一段的和最大
 8 那么我们把每个连续的一段都枚举出来,然后来算出里面的和,找出最大值即可
 9 
10 所以在这个需求下:
11 我们需要枚举每一段的起点、每一段的终点
12 然后对这一段进行求和
13 
14 枚举变量:每一段的起点、终点
15 枚举范围:起点:1-n,终点:起点-n
16 枚举判断条件:
17 求和得到每一段的和,在这些和里面选出最大的
18 
19 时间复杂度:
20 O(n^3)
21 
22 算法思路:
23 1、枚举每一段的起点和终点
24 2、对每一段进行求和,在这些和里面选出最大的
25 
26 */
27 #include <iostream>
28 using namespace std;
29 int a[200005];
30 int main(){
31     int n;
32     cin>>n;
33     int maxx=-0x7fffffff;
34     for(int i=1;i<=n;i++){
35         cin>>a[i];
36     }
37     //1、枚举每一段的起点和终点
38     for(int i=1;i<=n;i++){
39         for(int j=i;j<=n;j++){
40             //2、对每一段进行求和,在这些和里面选出最大的
41             int sum=0;
42             for(int k=i;k<=j;k++){
43                 sum+=a[k];
44             }
45             if(sum>maxx) maxx=sum;
46         }
47     }
48     cout<<maxx<<endl;
49     return 0;
50 }

3、枚举优化

 1 /*
 2 枚举优化
 3 
 4 可以把求和的那层循环去掉,我们可以对数据做预处理
 5 用s[i]表示第一个数到第i个数这个序列的和
 6 
 7 那么求s[i-j](第i个数到第j个数这个序列的和)的时候,
 8 可以直接用s[j]-s[i]+a[i]即可
 9 s[j]-s[i]表示的是i+1到j这个序列的和,所以需要加上a[i]
10 
11 现在的时间复杂度:
12 O(n)+O(n^2)=O(n^2)
13 
14 优化方法:
15 减少重复计算
16 
17 
18 */
19 #include <iostream>
20 using namespace std;
21 int a[200005];
22 int s[200005]={0};
23 int main(){
24     int n;
25     cin>>n;
26     int maxx=-0x7fffffff;
27     for(int i=1;i<=n;i++){
28         cin>>a[i];
29         s[i]=s[i-1]+a[i];
30     }
31     //1、枚举每一段的起点和终点
32     for(int i=1;i<=n;i++){
33         for(int j=i;j<=n;j++){
34             //2、对每一段进行求和,在这些和里面选出最大的
35             int sum=s[j]-s[i]+a[i];
36             if(sum>maxx) maxx=sum;
37         }
38     }
39     cout<<maxx<<endl;
40     return 0;
41 }

4、分治解法

  1 /*
  2 
  3 样例
  4 7
  5 2 -4 3 -1 2 -4 3
  6 
  7 分治解法
  8 假定a[1]-a[n]的序列对应的区间[l...r],其中间位置为mid,其最大和的子序列为[i...j]。
  9 那么显然,最大连续子序列的位置只有三种可能:
 10 ①完全处于序列的左半:l<=i<=j<=mid
 11 ②跨越序列中间:i<=mid<=j<=r
 12 ③完全处于序列的右半:mid<i<=j<=r
 13 
 14 
 15 只需要分别求出三种情况下的值,取他们最大的即可。
 16 其中,很容易求出第二种情况,第二种情况也就是包含mid的子序列,
 17 也就是[i...mid...j],而求[i...mid...j]的最大值,
 18 即求出区间[i..mid]的最大值maxx1与区间[mid..j]的最大值maxx2,将其合并即可。
 19 合并之后就变成了[i...mid mid...j],mid出现了两次,要减掉一次
 20 所以[i...mid...j]的最大值就是maxx1+maxx2-mid
 21 
 22 复杂度O(n)
 23 如何处理第一种和第三种情况呢?
 24 也不难发现,
 25 第一种情况,其实就是求区间[l..mid]中的最大值,
 26 第三种情况就是求区间[mid+1..r]中的最大值。那么,只需递归求出即可。
 27 显然,该解法的复杂度为O(nlogn)通过此题是没问题的。
 28 
 29 
 30 算法时间复杂度
 31 O(nlogn):二分是logn,处理第二种情况是n,所以合起来就是O(nlogn)
 32 
 33 
 34 如何求区间[i..mid]的最大值与区间[mid..j]的最大值,
 35 换句话说,也就是如何求以mid为尾的子序列的最大值 和 以mid为头的子序列的最大值
 36 先说以mid为头的子序列的最大和
 37 也就是[mid],[mid...mid+1],[mid...mid+2]......[mid...mid+j]这些序列里面的最大值
 38 int maxx2=-0x7fffffff;
 39 int sum2=0;
 40 for(int k=mid;k<=j;k++){
 41     sum2+=a[k];
 42     maxx2=max(sum2,maxx2);
 43 }
 44 
 45 求以mid为尾的子序列的最大和
 46 int maxx1=-0x7fffffff;
 47 int sum1=0;
 48 for(int k=mid;k>=i;k--){
 49     sum1+=a[k];
 50     maxx1=max(sum1,maxx1);
 51 }
 52 
 53 maxx1+maxx2-a[mid]
 54 
 55 
 56 递归做分治:
 57 a、递归的终止条件:
 58 因为我们的递归是为了求l到r序列的子序列的最大值,
 59 所以当区间只有一个元素时,就是终止条件,那个元素就是子序列的最大值
 60 b、递归的递推表达式:比较方式1、2、3的最大值。第2种跨越mid值的需要我们去计算,1,3种情况又转化成了子问题
 61 c、递归的返回值:子序列的最大和
 62 
 63 
 64 算法步骤:
 65 1、计算第二种跨越mid情况的序列的最大和
 66 2、比较方式1、2、3的最大值
 67 
 68 
 69 
 70 样例:
 71 4
 72 -1 3 -1 -2
 73 结果是3 
 74 
 75 mid=(1+4)/2 2
 76 ①完全处于序列的左半:l...mid:-1 3  对应的是3
 77 ②跨越序列中间:3+3-3=3
 78 ③完全处于序列的右半:mid+1...r:-1 -2 对应的结果是-1
 79 
 80 -1 3
 81 mid=1
 82 ①完全处于序列的左半:l...mid:-1
 83 ②跨越序列中间:-1+2-(-1)=2
 84 ③完全处于序列的右半:mid+1...r:3
 85 
 86 */
 87 #include <iostream>
 88 #include <algorithm>
 89 using namespace std;
 90 int a[200005];
 91 //分治(二分)求最大连续子序列的和
 92 int find(int l,int r){
 93     if(l==r) return a[l];
 94     int mid=(l+r)/2;
 95     //1、计算第二种跨越mid情况的序列的最大和
 96     //a、求以mid为尾的子序列的最大和
 97     int maxx1=-0x7fffffff;
 98     int sum1=0;
 99     for(int k=mid;k>=l;k--){
100         sum1+=a[k];
101         maxx1=max(sum1,maxx1);
102     }
103 
104     //b、求以mid为头的子序列的最大和
105     int maxx2=-0x7fffffff;
106     int sum2=0;
107     for(int k=mid;k<=r;k++){
108         sum2+=a[k];
109         maxx2=max(sum2,maxx2);
110     }
111 
112     //2、比较方式1、2、3的最大值
113     return max(max(find(l,mid),find(mid+1,r)),maxx1+maxx2-a[mid]);
114 }
115 
116 int main(){
117     int n;
118     cin>>n;
119     for(int i=1;i<=n;i++){
120         cin>>a[i];
121     }
122     cout<<find(1,n)<<endl;
123     return 0;
124 }

5、没用好分治创造的信息的分治法

下面代码是没用用好分治创造的信息的分治法代码,只能过两个点
而用好分治法创造的信息的分治法代码,可以过所有点

  1 /*
  2 
  3 本题分治优化原理
  4 
  5 比如贪心能够优化,是因为贪心着眼于局部的最优策略,
  6 只枚举了极少的局部的情况,所以贪心法有时候不一定对,但是一般效率都还可以
  7 动态规划能够优化,是因为找准了状态之间的转移关系,并且存储了中间的状态,
  8 减少了大量重复求状态的计算,所以动态规划一般效率非常高
  9 
 10 
 11 为什么这道题目(最大连续子序列和)使用分治能够进行优化,
 12 其实分治本身只是一种策略,告诉我们要如何去枚举,分治本身并不减少枚举的次数
 13 所以分治能够得到正确的解,肯定也是枚举了所有的情况,
 14 那为什么分治就过了所有的点,
 15 也就是对比枚举优化的O(n^2)的算法(也是枚举了所有的情况),
 16 分治为什么能变成O(nlogn),
 17 O(nlogn)相比于O(n^2)肯定是少枚举了很多情况
 18 而我们的分治算法又是对的,
 19 那说明分治算法少枚举的情况都是一些无关紧要的情况,
 20 现在的问题就是,
 21 分治到底少枚举了哪些情况
 22 也就是为什么分治可以优化
 23 
 24 可以从以下两个点来分析
 25 (1)、分治将问题规模变小,将问题的规模变小之后,有些时候需要枚举的情况也会变少,
 26 a、假设分治内部用到的算法是O(n^2),假设n是10,原先的10^2=100>二分后的2*5^2=50
 27 b、假设分治内部用到的算法是O(n),假设n是10,原先的10>=二分后的2*5
 28 a里面看似减少了枚举情况,其实并没有,因为减少的情况跑到②跨越序列中间:i<=mid<=j<=r
 29 所以分治将问题的规模变小并没有减少枚举的情况
 30 
 31 (2)、情况②跨越序列中间的算法是O(n)的算法-->(分治优化的关键)
 32 第二种情况:子序列一定包含mid
 33 (转换成)===>
 34 即求出区间[i..mid]的最大值与区间[mid..j]的最大值,将其合并即可
 35 
 36 那么我们枚举包含mid的子序列的算法是O(n^2)
 37 枚举变量:起点和终点
 38 枚举范围:起点:i...mid,终点:mid...j
 39 
 40 结论:
 41 分治本身不能优化算法,
 42 因为分治还是需要将所有可能的情况枚举出来,选最优解,
 43 而分治真正能够优化算法的是:分治里面应用到的策略
 44 
 45 我们在分治的过程中创造了信息
 46 我们在用分治算法的时候,就创造了下面这些信息
 47 子序列的情况只能是这三种情况里面的一种
 48 ①完全处于序列的左半:l<=i<=j<=mid
 49 ②跨越序列中间:i<=mid<=j<=r
 50 ③完全处于序列的右半:mid<i<=j<=r
 51 
 52 (所以这题就分成三种情况,情况1和3都是递归子问题,
 53 而情况2,利用分治的信息(包含mid),成功的将枚举O(n^2)的算法优化到了O(n),
 54 自然就减少了一些不必要的枚举的情况)
 55 
 56 强调:
 57 分治能够优化,不在与分治这种策略,而是这种分治策略创造了信息,
 58 让我们可以拿这个信息去优化枚举
 59 
 60 我们之前反复强调,优化枚举法,需要就是信息、关系式、等式,
 61 而分治法优化的实质就是分治的过程中给我们创造信息,创造了关系式,
 62 从而减少枚举情况
 63 
 64 
 65 枚举法能够优化的实质是什么
 66 枚举法能够优化的实质是有信息(关系式、等式、条件)可以让我们减少枚举情况(减少枚举范围、减少枚举变量、减少不必要的枚举)
 67 比如在这个题里面,没有信息,我们通过分治就创造了这些信息,从而通过分治法优化了枚举
 68 
 69 
 70 分治能够优化枚举的实质是什么
 71 a、分治其实只是一种枚举策略,只能改变枚举的方式,并不能减少枚举的次数
 72 b、分治能够优化枚举,不在于分治这种策略,而是这种分治策略创造了信息(比如本题第二种情况子序列一定包含mid),让我们可以拿这个信息去优化枚举
 73 
 74 
 75 */
 76 
 77 //下面代码是没用好分治创造的信息的分治法代码,只能过两个点
 78 //而用好分治法创造的信息的分治法代码,可以过所有点
 79 #include <iostream>
 80 #include <algorithm>
 81 using namespace std;
 82 int a[200005];
 83 int s[200005]={0};
 84 //分治(二分)求最大连续子序列的和
 85 int find(int l,int r){
 86     if(l==r) return a[l];
 87     int mid=(l+r)/2;
 88     //1、计算第二种跨越mid情况的序列的最大和
 89     int maxx=-0x7fffffff;
 90     for(int i=l;i<=mid;i++){
 91         for(int j=mid;j<=r;j++){
 92             //2、对每一段进行求和,在这些和里面选出最大的
 93             int sum=s[j]-s[i]+a[i];
 94             if(sum>maxx) maxx=sum;
 95         }
 96     }
 97 
 98     //2、比较方式1、2、3的最大值
 99     return max(max(find(l,mid),find(mid+1,r)),maxx);
100 }
101 
102 int main(){
103     int n;
104     cin>>n;
105     for(int i=1;i<=n;i++){
106         cin>>a[i];
107         s[i]=s[i-1]+a[i];
108     }
109     cout<<find(1,n)<<endl;
110     return 0;
111 }

6、贪心解法

 1 /*
 2 
 3 贪心是每次选择的是局部最优解,
 4 首先开始,只有a[1],那么这个a[1]就是最优解,也就是连续非空子序列中和最大的
 5 然后我们每增加一个数a[i],
 6 那么对a[i]而言(也就是说如果我们在子序列中必选a[i]的时候),
 7 如果a[i]之前的序列和大于0,a[i]加上它可以组成子序列的和肯定比只有a[i]更大,
 8 也就是a[i]<a[i]+s[i-1]
 9 如果a[i]之前的序列和小于等于0,那么a[i]完全可以不要之前的序列,
10 也就是a[i]>=a[i]+s[i-1]
11 
12 这里表示的a[i]之前的序列,说的是以a[i-1]结尾的连续非空子序列中和最大的序列s[i-1]
13 
14 我们这里的对a[i]而言,也就是假设了必定选中a[i]的情况
15 换言之,我们上面贪心的过程求到的最优解就是以a[i]为结尾的子序列和s[i]最大的的局部最优解
16 而这里,我们在所有的s[i]中找最大的,即max(s[i]|1<=i<=n),
17 那么就可以求出a[1]-a[n]所有子序列的全局最优解
18 所以这个题目是可以用贪心来做的,就是可以由局部最优解得到全局最优解
19 
20 
21 强调一遍 这题为什么可以用贪心来做:
22 因为局部最优解表示的是以a[i]为结尾的子序列和最大的s[i],
23 而全局最优解直接在所有的s[i]中找最大的就可以了
24 我们可以由局部最优解,得到全局最优解,自然就可以用贪心来做
25 
26 
27 一个题目能不能用贪心来做的依据
28 因为贪心是“鼠目寸光”,求的是局部最优解,
29 如果局部最优解能够得到全局最优解,那么一个题目就能用贪心来做
30 
31 
32 算法步骤:
33 1、找到以a[i-1]结尾的连续非空子序列中和最大s[i-1]
34 2、
35 如果这个序列s[i-1]为负数,那么以a[i]结尾的连续非空子序列中和最大的序列就是a[i]本身(本身我们可以看做+0)
36 如果这个序列s[i-1]为正数,那么以a[i]结尾的连续非空子序列中和最大的序列就是a[i]+s[i-1]
37 3、在所有的s[i]中找最大的,即max(s[i]|1<=i<=n)
38 
39 
40 如果用贪心来求这个问题(最大连续子序列的和),输入的数全是负数怎么办,会不会出错
41 肯定是不会出错的,因为我们贪心的过程是找所有以a[i]结尾的子序列和最大的s[i],
42 而得到全局最优解的过程是在s[i]中找最大的,这样囊括了所有的情况,所以肯定是对的
43 
44 贪心的时间复杂度:
45 O(n)
46 
47 贪心优化的原理
48 贪心能够优化枚举法,是因为贪心着眼于局部的最优策略,
49 只枚举了极少的局部的情况,所以贪心法有时候不一定对,但是一般效率都还可以
50 
51 
52 */
53 #include <iostream>
54 #include <algorithm>
55 using namespace std;
56 int a[200005];
57 int main(){
58     int n;
59     cin>>n;
60     for(int i=1;i<=n;i++){
61         cin>>a[i];
62     }
63     int sum=a[1];
64     int maxx=a[1];
65     for(int i=2;i<=n;i++){
66         if(sum<=0) sum=0;
67         sum+=a[i];
68         maxx=max(sum,maxx);
69     }
70     cout<<maxx<<endl;
71     return 0;
72 }

7、贪心优化

 1 /*
 2 
 3 上面贪心代码从代码层面上来看是可以优化的
 4 1、时间方面优化:循环可以合并(循环方向一致,循环最大值也是,并且两个循环之间没有什么逻辑操作代码)
 5 2、空间方面优化:代码中只用到了a[i],所以a[]数组可以用一个变量来代替
 6 
 7 */
 8 #include <iostream>
 9 #include <algorithm>
10 using namespace std;
11 int main(){
12     int n;
13     cin>>n;
14     int sum;
15     cin>>sum;
16     int maxx=sum;
17     for(int i=2;i<=n;i++){
18         int x;
19         cin>>x;
20         if(sum<=0) sum=0;
21         sum+=x;
22         maxx=max(sum,maxx);
23     }
24     cout<<maxx<<endl;
25     return 0;
26 }

8、动态规划解法

 1 /*
 2 动态规划解法
 3 
 4 这道题目,也可以用动态规划来做
 5 
 6 
 7 以第i个数结尾的最大连续子序列的和,只存在两种选择:
 8 情形1:只包含a[i]
 9 情形2:包含a[i]和以a[i-1]结尾的最大连续和序列
10 
11 所以f[i]表示以第i个元素结尾的子序列的最大值 
12 
13 f[i]=max(f[i-1]+a[i],a[i]) (2<=i<=n)
14 
15 所以要求所有序列的最大值,就是
16 Answer=max{f[i]|1<=i<=n}
17 
18 初始状态:f[1]=a[1]
19 
20 算法步骤:
21 1、确定动态规划初始条件:f[1]=a[1]
22 2、动态规划操作:f[i]=max(f[i-1]+a[i],a[i]) (2<=i<=n)
23 3、求ans:Answer=max{f[i]|1<=i<=n}
24 
25 动态规划能够优化枚举的原理
26 动态规划能够优化,是因为找准了状态之间的转移关系(找到了题目的规律),并且存储了中间的状态,
27 减少了大量重复求状态的计算,所以动态规划一般效率非常高
28 
29 */
30 
31 //代码层面优化的代码
32 #include <iostream>
33 #include <algorithm>
34 using namespace std;
35 int a[200005];
36 int f[200005]={0};
37 int main(){
38     int n;
39     cin>>n;
40     cin>>a[1];
41     f[1]=a[1];
42     //1、确定动态规划初始条件:f[1]=a[1]
43     int maxx=f[1];
44     for(int i=2;i<=n;i++){
45         cin>>a[i];
46         //2、动态规划操作:f[i]=max(f[i-1]+a[i],a[i]) (2<=i<=n)
47         f[i]=max(f[i-1]+a[i],a[i]);
48         //3、求ans:Answer=max{f[i]|1<=i<=n}
49         maxx=max(f[i],maxx);
50     }
51     cout<<maxx<<endl;
52     return 0;
53 }
54 
55 //未从代码层面优化的代码
56 // #include <iostream>
57 // #include <algorithm>
58 // using namespace std;
59 // int a[200005];
60 // int f[200005]={0};
61 // int main(){
62 //     int n;
63 //     cin>>n;
64 //     for(int i=1;i<=n;i++){
65 //         cin>>a[i];
66 //     }
67 //     //1、确定动态规划初始条件:f[1]=a[1]
68 //     f[1]=a[1];
69 //     //2、动态规划操作:f[i]=max(f[i-1]+a[i],a[i]) (2<=i<=n)
70 //     for(int i=2;i<=n;i++){
71 //         f[i]=max(f[i-1]+a[i],a[i]);
72 //     }
73 //     //3、求ans:Answer=max{f[i]|1<=i<=n}
74 //     int maxx=-0x7fffffff;
75 //     for(int i=1;i<=n;i++){
76 //         maxx=max(f[i],maxx);
77 //     }
78 //     cout<<maxx<<endl;
79 //     return 0;
80 // }

9、贪心和dp区别

/*

为了帮助大家理解本题贪心和动态规划的区别,
下面给出一个例子加以说明。

有一个3*3的方格,每个格子有一个正整数,
规定从左下角出发,每次只能向上或向右移动一格,
现在要求找出一条路径使得从左下角到达右上角所经过的格子中的数字之和最大,
求最大值。

20   9   8
 6   7   5
 3  14  12

贪心:一直选择局部最优解
3→14→12→5→8=42

最优解:3→6→20→9→8=46

如果是动态规划,会怎么做
动态规划是全局的找规律,
这个规律就是对于一个格子,只有向上和向右可以到达它,
通过这个规律,思考状态和状态转移方程
状态:f[i][j]表示走到第i行第j列位置的路径数字和的最大值
状态转移方程:
f[i][j]=max(f[i-1][j] , f[i][j-1])+a[i][j]
f[i-1][j]表示向上走
f[i][j-1]表示向右走

而对应到本题中
贪心只是考虑到了当前对于a[i]最优的情况,
是选择s[i-1]还是不选择s[i-1]来得到局部最优解

动态规划却是在全局的基础上,
找到了规律:
以第i个数结尾的最大连续子序列的和,只存在两种选择:
情形1:只包含a[i]
情形2:包含a[i]和以a[i-1]结尾的最大连续和序列

通过规律设置好了状态,找到状态转移的方程
状态:f[i]表示以第i个元素结尾的子序列的最大值 
状态转移方程:f[i]=max(f[i-1]+n[i],n[i]) (2<=i<=n)
本题解:Answer=max{f[i]|1<=i<=n}

总结:
最大字段和的贪心解法就是只考虑当前对于a[i]最优的情况,是选择s[i-1]还是不选择s[i-1]来得到局部最优解
最大字段和的动态规划解法就是在全局统筹的基础上,找到规律,通过规律设置好状态,找到状态转移的方程

*/

10、动态规划的滚动数组优化

 1 /*
 2 
 3 我们来看一眼代码:
 4 代码中用到a数组位置除了a[1]这个固定的,剩下的就是a[i]
 5 所以a数组可以被一个变量代替。
 6 
 7 
 8 然后再来看一眼f数组
 9 f数组:全程我们只用到了f[i]元素和f[i-1]元素
10 是不是闻到了滚动数组的气息
11 最终我们可以得出空间优化版
12 
13 */
14 #include <iostream>
15 #include <algorithm>
16 using namespace std;
17 int f[2]={0};
18 int main(){
19     int n;
20     cin>>n;
21     cin>>f[1];
22     //1、确定动态规划初始条件:f[1]=a[1]
23     int maxx=f[1];
24     for(int i=2;i<=n;i++){
25         int x;
26         cin>>x;
27         //2、动态规划操作:f[i]=max(f[i-1]+a[i],a[i]) (2<=i<=n)
28         f[i%2]=max(f[!(i%2)]+x,x);
29         //3、求ans:Answer=max{f[i]|1<=i<=n}
30         maxx=max(f[i%2],maxx);
31     }
32     cout<<maxx<<endl;
33     return 0;
34 }

 
原文地址:https://www.cnblogs.com/Renyi-Fan/p/13038663.html