最大连续和问题
給出一個長度為n的序列A1,A2,…,An,求最大連續和。換句話說,要求找到1<=i<=j<=n,使得Ai+Ai+1+…Aj盡量大。
暴力枚舉實現
#include <iostream> using namespace std; int A[50]={5,9,7,3,4,1,5,8,6,7,3,6,4,0,8,6,7}; int main() {int n=50;int tot=0;int best=A[0];//初始化最大值,為了防止連續和小于0,所以必須初始化為A[0]。for(int i=0;i<n;i++){for(int j=i;j<n;j++){//檢查連續子序列A[i],…,A[j]int sum=0;for(int k=i;k<=j;k++){//累加元素和sum+=A[k];tot++;}if(sum>best){best=sum;}}}cout<<"best="<<best<<' '<<"tot="<<tot<<endl;return 0; }tot與機器的運行速度無關。
不同機器的速度不一樣,運行時間也會有所差異,但tot的值一定相同。
換句話說,它去掉了機器相關的因素,只衡量算法的“工作量”大小,
具體來說,是“加法”操作的次數。
算法分析:
1.第一層循環遍歷數組A的i位從0到n,是最大連續和的左起點;
2.第二層循環遍歷數組A的j位從i到n,是最大連續和的右終點;
3.第三層循環遍歷數組A的k位從i到j,求出當前范圍內的連續和;
4.公式:設輸入規模為n時的加法操作次數
T(n)= ∑i=1n∑j=inj?i+1\displaystyle\sum_{i=1}^{n} \displaystyle\sum_{j=i}^{n} j-i+1i=1∑n?j=i∑n?j?i+1=∑i=1n\displaystyle\sum_{i=1}^{n}i=1∑n? (n?i+1)(n?i+2)2\frac{(n-i+1)(n-i+2)}{2}2(n?i+1)(n?i+2)?=n(n+1)(n+2)6\frac{n(n+1)(n+2)}{6}6n(n+1)(n+2)?
連續子序列之和等于兩個前綴和之差實現
#include <iostream> using namespace std; int A[50]={5,9,7,3,4,1,5,8,6,7,3,6,4,0,8,6,7}; int S[50]; int main() {int n=50,best=A[0];S[0]=0;for(int i=0;i<n;i++){S[i]=S[i-1]+A[i];//遞推前綴和S}for(int i=0;i<n;i++){for(int j=i;j<n;j++){best=max(best,S[j]-S[i-1]);//更新最大值}}cout<<"best="<<best<<endl;return 0; }算法分析:
S[i]表示第1個數到第i個數的和,S[j]-S[i-1]表示第i個數到第j個數列的和。
T(n)=∑i=1nn?i+1\displaystyle\sum_{i=1}^{n} n-i+1i=1∑n?n?i+1=n(n+1)2\frac {n(n+1)}{2}2n(n+1)?
分治算法實現
#include <iostream> using namespace std; int A[50]={5,9,7,3,4,1,5,8,6,7,3,6,4,0,8,6,7}; int maxsum(int *A,int x,int y) {//返回數組在左閉右開區間[x,y)中的最大連續和if(y-x==1) return A[x]; //只有一個元素,直接返回。int m=x+(y-x)/2;//分治第一步:劃分成[x,m)和[m,y)int maxs=max(maxsum(A,x,m),maxsum(A,m,y));//分治第二步:遞歸求解int v,L,R;v=0,L=A[m-1];//分治第三步:合并(1)——從分界點開始往左的最大連續和Lfor(int i=m-1;i>=x;i--){L=max(L,v+=A[i]);}v=0,R=A[m];//分支第三步:合并(2)——從分界點開始往右的最大連續和Rfor(int i=m;i<y;i++){R=max(R,v+=A[i]);}return max(maxs,L+R); //把子問題的解與L和R比較 } int main() {cout<<maxsum(A,0,51)<<endl;return 0; }分治算法實現:
1.劃分問題:把序列分成元素個數盡量相等的兩半;
2.遞歸求解:分別求出完全位于左半或者完全位于右半的最佳序列;
3.合并問題:求出起點位于左半,終點位于右半的最大連續和序列,并和子問題的最優解比較。
思路分析
首先,我們可以把整個數列平均分成左右兩部分,答案則會在以下三種情況中:
1、所求數列完全包含在左半部分的數列中。
2、所求數列完全包含在右半部分的數列中。
3、所求數列剛好橫跨分割點,即左右數列各占一部分。
前兩種情況和大問題一樣,只是規模小了些,如果三個子問題都能解決,那么答案就是三個結果的最大值。
計算出:以分割點為起點向左的最大連續數列和、以分割點為起點向右的最大連續數列和,這兩個結果的和就是第三種情況的答案。因為已知起點,所以這兩個結果都能在O(N)的時間復雜度能算出來。
算法分析:
用遞歸的思路進行分析,設序列長度為n時T(n)=2T(n/2)+n,T(1)=1;
其中2T(n/2)是兩次長度為n/2的遞歸調用,而最后的n是合并的時間(整個序列恰好掃描一遍)。
注意分界點應當是x和y的平均數m=x+(y-x)/2,這是因為運算符“/”的“取整”朝零方向(towards zero)
的取整,而不是向下取整用x+(y-x)/2來確保分界點總是靠近區間起點。
動態規劃實現
#include <iostream> using namespace std; int A[50]={0,5,9,7,3,4,1,5,8,6,7,3,6,4,0,8,6,7}; int main() {int ans=A[1];for(int i=1;i<50;i++){if(A[i-1]>0)A[i]+=A[i-1];else A[i]+=0;if(A[i]>ans) ans=A[i];}cout<<ans<<endl;return 0; }算法分析
用dp[n]表示以第n個數結尾的最大連續子數列的和,于是存在以下遞推公式:
dp[n] = max(0, dp[n-1]) + num[n]
則整個問題的答案是max(dp[m]) | m∈[1, N]。
大道至簡——完美解決實現
#include <iostream> using namespace std; int main() {int N,n,s,ans,m=0;cin>>N>>n; //讀取數組長度和數列中的第一個數ans=s=n; //把ans初始化為數列中的的第一個數for(int i=1;i<N;i++){if(s<m) m=s;cin>>n; s+=n;if(s-m>ans)ans=s-m;}cout<<ans<<endl;return 0; }已知一個sum數組,sum[i]表示第1個數到第i個數的和,于是sum[j] - sum[i-1]表示第i個數到第j個數的和。
以第n個數為結尾的最大子數列,假設這個子數列的起點是m,于是結果為sum[n] - sum[m-1]。
并且,sum[m]必然是sum[1],sum[2]…sum[n-1]中的最小值。
這樣,如果在維護計算sum數組的時候,同時維護之前的最小值, 那么答案也就出來了。
在計算前綴和的過程中維護之前得到的最小值。
它的時間復雜度是O(N),空間復雜度是O(1),這達到了理論下限!
最大連續和問題,解決!
總結
- 上一篇: 《机器学习》 —— 第一章:绪论 学习
- 下一篇: PAT (Basic Level) Pr