最长子序列和问题
最长子序列和问题
本随笔写于20200121
问题描述:

算法1:枚举法
算法描述:以2 -3 -4 13 -5 2 -5 -3 12 -9 为例,分别计算以2,-3,-4…,-9开头的所有子序列和,如果新子序列的和大于当前最大值,则用新子序列的和替代当前最大值。最长序列的下标的更新就发生在最大值的更新过程中。
time cost : 枚举左端点和右端点,需要O(n2)的复杂度;根据计算新子序列之和的方式,总复杂度不一样:
(a) 计算新子序列之和方式为 从新子序列头加到尾,计算复杂度为O(n),总时间复杂度为O(n3);
(b) 计算新子序列之和方式为 新子序列和 = 旧子序列和 + 新增加的元素 - 新减少的元素,计算时间复杂度为O(1),总时间复杂度为O(n2)
代码实现:
#include<iostream> using namespace std; int maxm,a[100],sum; int start,end;// record the index of the subsequence int main(){ int n; cin>>n; for(int i=0;i<n;i++) cin>>a[i]; for(int i=0;i<n;i++){ sum = a[i]; if(sum > maxm) { maxm = sum; start = i; end = i; } for(int j=i+1;j<n;j++){ sum += a[j]; if(sum > maxm) { maxm = sum; start = i; end = j; } } } cout<<maxm<<endl; cout<<"from "<<start<<" to "<<end; return 0; }
算法2:动态规划
算法描述:用变量sum记录当前子序列累加和,用maxm记录最大值。
从头开始进行累加,如果sum>max,则max = sum;如果sum<0,则将sum置0。
最长序列尾端下标的更新,发生在最大值更新过程中;
最长序列前端下标的更新,发生在第一个非负数时,因此下标为sum<0时的下标加一。
理解:最长子序列和的子序列肯定以正数开头,所以如果sum是负数,不妨将其设为0,等待下一个正数的到来。
time cost : O(n) (从头到尾只进行了一次扫描)
代码实现:
#include<iostream> using namespace std; int maxm,a[100],sum; int start,end;// record the index of the subsequence int main(){ int n; cin>>n; for(int i=0;i<n;i++) cin>>a[i]; for(int i=0;i<n;i++){ sum += a[i]; if(sum > maxm) { maxm = sum; end = i; } else if(sum < 0) { sum = 0; start = i+1; } } cout<<maxm<<endl; cout<<"from "<<start<<" to "<<end; }
另一种算法描述(更容易理解):见《算法笔记》P430;状态的无后效性 :P431
算法3:分治法(递归)
分治算法的核心是把问题分成两个大致相等的子问题,然后递归对它们求解,这是“分”的部分,在“治”这一阶段将两个子问题的解合并到一起求解。
算法描述:把数组分割成两部分,左半部分和右半部分,最大子序列出现的位置可能在:左半部、右半部、跨越分割的位置占据左右半部。
对于横跨整个序列的情况,我们采用以下方法:从中间开始,分别向左、向右遍历,分别得到向左、向右遍历的最大值,将这两个最大值相加得到跨越分割的位置占据左右半部的最大值,再与左半部、右半部的最大值比较,找到三者中的最大值,即为整个序列的最大值。[2]
#include<iostream> using namespace std; int a[100]; int ans,start,end,state; int maxm(int a,int b,int c){ if(a>b && a>c) return a; else if(b>a && b>c) return b; else if(c>a && c>b) return c; } int merge(int left,int right){ if(left == right) return a[left]; int mid = (left+right) / 2; int t1 = merge(left,mid); int t2 = merge(mid+1,right); int suml = 0; int sumr = 0; int maxl = 0; int maxr = 0; for(int i=mid;i>=left;i--){ suml += a[i]; if(suml > maxl) { maxl = suml; start = i; } } for(int i = mid+1;i<=right;i++){ sumr += a[i]; if(sumr > maxr) { maxr = sumr; end = i; } } return maxm(t1,t2,maxl+maxr); } int main(){ int n; cin>>n; for(int i =0;i<n;i++){ cin>>a[i]; } ans = merge(0,n-1); cout<<ans<<endl; cout<<"from "<<start<<" to "<<end; return 0; }
time cost : O(nlogn)
测试数据 :
//test1 10 2 -3 -4 13 -5 2 -5 -3 12 -9 //test2 6 -2 11 -4 13 -5 -2 //test3 8 2 -3 -4 13 -5 2 -5 12
如何理解递归?[3]
看递归程序不要老想着它是怎么执行的,没必要看成“不停调用函数”。基本原理同数学归纳法。
递归程序理解两点:
1.只要假设第一句求出了前半段的最大子序列和,第二句求出了后半段的,而下面的合并过程在上面假设的基础上能正确执行。
2.终止条件,并且终止处是满足上面假设的。
练习题目:
http://codeup.cn/problem.php?cid=100000626&pid=0
Reference:
[1]《算法笔记》胡凡 P430-431
[2] 分治法算法思想:https://www.jianshu.com/p/c4d68d0376ad
[3] 如何理解递归:https://bbs.csdn.net/topics/300115494?locationNum=8&fps=1

浙公网安备 33010602011771号