最大子段和詳解

來源:互聯網
上載者:User

最大子段和問題(Maximum Interval Sum)

(有時也稱LIS)

經典的動態規劃問題,幾乎所有的演算法教材都會提到.本文將分析最大子段和問題的幾種不同效率的解法,以及最大子段和問題的擴充和運用. 一.問題描述

給定長度為n的整數序列,a[1...n], 求[1,n]某個子區間[i , j]使得a[i]+…+a[j]和最大.或者求出最大的這個和.例如(-2,11,-4,13,-5,2)的最大子段和為20,所求子區間為[2,4].
二. 問題分析 1.窮舉法

窮舉應當是每個人都要學會的一種方式,這裡實際上是要窮舉所有的[1,n]之間的區間,所以我們用兩重迴圈,可以很輕易地做到遍曆所有子區間,一個表示起始位置,一個表示終點位置.代碼如下:

int start = 0;//起始位置int end = 0;  //結束位置int max = 0;for(int i = 1; i <= n; ++i){    for(int j = i; j <= n;++j)    {        int sum = 0;        for(int k = i; k <=j; ++k)            sum += a[k];        if(sum > max)        {           start = i;           end = j;           max = sum;        }    }} 

這個演算法是幾乎所有人都能想到的,它所需要的計算時間是O(n^3).當然,這個代碼還可以做點最佳化,實際上我們並不需要每次都重新從起始位置求和加到終點位置.可以充分利用之前的計算結果.

或者我們換一種窮舉思路,對於起點 i,我們遍曆所有長度為1,2,…,n-i+1的子區間和,以求得和最大的一個.這樣也遍曆了所有的起點的不同長度的子區間,同時,對於相同起點的不同長度的子區間,可以利用前面的計算結果來計算後面的.

比如,i為起點長度為2的子區間和就等於長度為1的子區間的和+a[i+1]即可,這樣就省掉了一個迴圈,計算時間複雜度減少到了O(n^2).代碼如下:

int start = 0;//起始位置int end = 0;//結束位置int max = 0;for(int i = 1; i <= n; ++i){    int sum = 0;    for(int j = i; j <= n;++j)    {        sum += a[j];        if(sum > max)        {           start = i;           end = j;           max = sum;        }    }} 


  2.分治法

求子區間及最大和,從結構上是非常適合分治法的,因為所有子區間[start, end]只可能有以下三種可能性: 在[1, n/2]這個地區內 在[n/2+1, n]這個地區內 起點位於[1,n/2],終點位於[n/2+1,n]內

以上三種情形的最大者,即為所求. 前兩種情形符合子問題遞迴特性,所以遞迴可以求出. 對於第三種情形,則需要單獨處理. 第三種情形必然包括了n/2和n/2+1兩個位置,這樣就可以利用第二種窮舉的思路求出: 以n/2為終點,往左移動擴張,求出和最大的一個left_max 以n/2+1為起點,往右移動擴張,求出和最大的一個right_max left_max+right_max是第三種情況可能的最大值

樣本:

int maxInterval(int *a, int left, int right) {    if(right==left)      return a[left]>0?a[left]:0;     int center = (left+right)/2;    //左邊區間的最大子段和    int leftMaxInterval = maxInterval(a,left,center);    //右邊區間的最大子段和    int rightMaxInterval= maxInterval(a,center+1,right);     //以下求端點分別位於不同部分的最大子段和     //center開始向左移動    int sum = 0;    int left_max = 0;    for(int i = center; i >= left; –i)    {       sum += a[i];       if(sum > left_max)          left_max = sum;    }    //center+1開始向右移動    sum = 0;    int right_max = 0;    for(int i = center+1; i <= right; ++i)    {       sum += a[i];       if(sum > right_max)         right_max = sum;    }    int ret = left_max+right_max;    if(ret < leftMaxInterval)        ret = leftMaxInterval;    if(ret < rightMaxInterval)        ret = rightMaxInterval;    return ret; } 


 

分治法的痛點在於第三種情形的理解,這裡應該抓住第三種情形的特點,也就是中間有兩個定點,然後分別往兩個方向擴張,以遍曆所有屬於第三種情形的子區間,求的最大的一個,如果要求得具體的區間,稍微對上述代碼做點修改即可. 分治法的計算時間複雜度為O(nlogn). 3.動態規劃法

動態規劃的基本原理這裡不再贅述,主要討論這個問題的建模過程和子問題結構.時刻記住一個前提,這裡是連續的區間 令b[j]表示以位置 j 為終點的所有子區間中和最大的一個 子問題:如j為終點的最大子區間包含了位置j-1,則以j-1為終點的最大子區間必然包括在其中 如果b[j-1] >0, 那麼顯然b[j] = b[j-1] + a[j],用之前最大的一個加上a[j]即可,因為a[j]必須包含 如果b[j-1]<=0,那麼b[j] = a[j] ,因為既然最大,前面的負數必然不能使你更大

對於這種子問題結構和最佳化問題的證明,可以參考演算法導論上的“剪下法”,即如果不包括子問題的最優解,把你假設的解粘帖上去,會得出子問題的最佳化矛盾.證明如下: 令a[x,y]表示a[x]+…+a[y] , y>=x 假設以j為終點的最大子區間 [s, j] 包含了j-1這個位置,以j-1為終點的最大子區間[ r, j-1]並不包含其中 即假設[r,j-1]不是[s,j]的子區間 存在s使得a[s, j-1]+a[j]為以j為終點的最大子段和,這裡的 r != s  由於[r, j -1]是最優解, 所以a[s,j-1]<a[r, j-1],所以a[s,j-1]+a[j]<a[r, j-1]+a[j] 與[s,j]為最優解矛盾.

執行個體:

int max = 0;int b[n+1];int start = 0;int end = 0;memset(b,0,n+1);for(int i = 1; i <= n; ++i) {   if(b[i-1]>0)   {     b[i] = b[i-1]+a[i];   }else{     b[i] = a[i];   }   if(b[i]>max)     max = b[i]; } 


動態規劃法的計算時間複雜度為O(n),是最優的解。做幾道題加深理解

最直白的LIS題:http://acm.hdu.edu.cn/showproblem.php?pid=1087

最大子段和升級版,最大M段和:http://acm.hdu.edu.cn/showproblem.php?pid=1024

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.