什么是最大連續子序列和問題?
問題描述:給定一個序列(整數或浮點數),求出其中連續的子序列和最大的那一個。
例:序列{-10 1 2 3 4 -5 -23 3 7 -21},其最大的連續子序列為{1 2 3 4}或{3 7},最大和為10.
方法一:暴力求解
最最普通的方法,效率十分低,一般不會用到,這里簡單介紹。直接兩個for循環枚舉子序列的首尾,再來一個for循環計算首尾之間的序列和,計算所有的序列和,找到最大值。
時間復雜度:O(n^3)
效率賊低,千萬不要用!(所就不貼代碼了)
方法二:預處理暴力求解
第一種方法為什么這么慢,原因之一是每次都要計算首尾之間的序列和。基於這個考慮,我們可以對數據進行預先處理:讀入數據時使用一個數組SUM[i]來記錄前i項數據之和。用這種方法,只需要兩個for循環枚舉子序列的首尾,利用SUM數組計算子序列和,找到最大值。
時間復雜度:O(n^2)
還是很糟糕,建議不要用。(所以也不貼代碼qwq)
方法三:分治法求解
把序列分成左右兩部分,一般對半分,數量不等也沒關系。最大子序列和的位置存在三種情況:1、完全在左半部分;2、完全在右半部分;3、跨越左右兩部分。分別求出左半部分的最大子序列和、右半部分的最大子序列和、以及跨越左右兩部分的最大子序列和,三者中的最大者就是要求的。
如何求三部分的最大子序列和呢?
左半部分的最大子序列和,可把左半部分作為新的輸入序列通過該算法遞歸求出。右半部分的最大子序列和同理。
接下來就是求解跨越左右兩部分的最大子序列和,也就是求出包含左半部分中最右邊元素的子序列的最大和,和包含右半部分中最左邊元素的子序列的最大和,將兩者相加即為跨越左右兩個部分的最大子序列和。具體見如下代碼:
//分治算法 int a[999999]; int MAxSubSum(int left,int right) { int sum=0; if(left==right)//基本情況:只有一個元素 sum=a[left]>0?a[left]:0; else { int center=(left+right)/2; int leftsum=MaxSubSum(left,center);//左半部分 int rightsum=MAxSubSum(center+1,right);//右半部分 //求包含左半部分最右元素的最大和 int s1=0; int lefts=0; for(int i=center;i>=left;i--) { lefts+=a[i]; if(lefts>s1) s1=lefts; } //求包含右半部分最左元素的最大和 int s2=0; int rights=0; for(int i=center+;i<=right;i++) { rights+=a[i]; if(rights>s2) s2=rights; } //取三者最大值 sum=s1+s2; if(sum<leftsum) sum=leftsum; if(sum<rightsum) sum=rightsum; } return sum; }
時間復雜度:O(nlgn)
運用遞歸思想,一層一層分解,最終解決問題。效率還好,有助於理解分治與遞歸,可以嘗試使用。
方法四:動態規划
這是最常見的方法了,簡單有效,好理解。
狀態轉移方程:sum[i] = max{sum[i-1]+a[i],a[i]}. (sum[i]記錄以a[i]為子序列末端的最大序子列連續和)
其實完全可以不用開數組,累計sum直到sum + a < a,把sum賦值為a,更新最大值就行了。
//動態規划算法 int MaxSum(int n) { int sum=0,b=0; for(int i=0;i<n;i++) { if(b>0) b+=a[i]; else b=a[i]; if(b>sum) sum=b; } return sum; }
時間復雜度:O(n)
這是好用的求最大子序列的方法了,牆裂推薦!
相關題目練習:
hdu 1003:http://acm.hdu.edu.cn/showproblem.php?pid=1003
hdu 1231:http://acm.hdu.edu.cn/showproblem.php?pid=1231
作者: AlvinZH
出處: http://www.cnblogs.com/AlvinZH/
本人Github:https://github.com/Pacsiy/JobDu
本文版權歸作者AlvinZH和博客園所有,歡迎轉載和商用,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利.