動態(tài)規(guī)劃之"最大連續(xù)子序列"

最大連續(xù)子序列問題

問題定義

給定K個整數(shù)的序列{ N1, N2, ..., Nk }应闯,其任意連續(xù)子序列可表示為{ Ni, Ni+1, ..., Nj }枪狂,其中 1 <= i <= j <= K嗓袱。最大連續(xù)子序列是所有連續(xù)子序列中元素和最大的一個宏胯, 例如給定序列{ -2, 11, -4, 13, -5, -2 }渔嚷,其最大連續(xù)子序列為{ 11, -4, 13 }谨湘,最大和為20

解法1:樸素解法, 時間復雜度 O(K^2)

//假設(shè)給定序列:a1,a2,...,aK
maxsum=0; // 最大的連續(xù)子序列的和
for(int i=0; i<K; i++){
    tmpSum=0;
    for(int j=i; j<K; j++){
        tmpSum += a[j]
        if(tmpSum > maxsum){
            maxsum = tmpSum;
        }
    }
}

解法2:分治算法, 時間復雜度:O(nlogn)

對于任意一個序列{a1, a2, ...,am,.... an}, ( m=(n+1)/2 ) 最大的連續(xù)子序列在該序列中的位置存在三種情況:

  1. 位于中間部分的左邊;
  2. 位于中間部分的右邊 ;
  3. 左邊和右邊都含有最大的連續(xù)子序列的一部分, e.g. ai, ..., am, ...., aj.

對于情況1,2, 使用遞歸算法可以輕松計算出谚鄙;對于情況3各拷, 則通過求出前半部分的最大和(包含前半部分的最后一個元素)以及后半部分的最大和(包含后半部分的第一個元素)而得到,然后將這兩個和在一起闷营, 最后烤黍,三種情況中最大的結(jié)果就是要求的結(jié)果。

int MaxSubSum(const int A[], int Left, int Right)
{
  int MaxLeftSum,MaxRightSum;
  int MaxLeftBorderSum,MaxRightBorderSum;
  int LeftBorderSum,RightBorderSum;
  int mid,i;
  
  if(Left == Right) // 處理只有一個元素的子序列
  {
    if(A[Left] > 0)
      return A[Left];
    else // 對于小于等于0的元素傻盟, 
      return 0;
  }
  
  mid= (Left + Right)/2;
  // 情況1
  MaxLeftSum = MaxSubSum(A,Left,mid);
  // 情況2
  MaxRightSum = MaxSubSum(A,mid+1,Right);
  
  // 情況3
  MaxLeftBorderSum = 0;
  LeftBorderSum = 0;
  for(i = mid;i >= Left;i--)// 求解最大序列的左邊部分
  {
    LeftBorderSum += A[i];
    if(LeftBorderSum > MaxLeftBorderSum)
      MaxLeftBorderSum = LeftBorderSum;
  }
  
  MaxRightBorderSum = 0;
  RightBorderSum = 0;
  for(i = mid+1;i <= Right;i++)// 求解最大序列的右邊部分
  {
    RightBorderSum += A[i];
    if(RightBorderSum > MaxRightBorderSum)
      MaxRightBorderSum = RightBorderSum;
  } 
  
  return Max(MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum); // 返回三種情況中最大的結(jié)果
}

解法3: 動態(tài)規(guī)劃 , 時間復雜度O(n)

引理1: 以負數(shù)開頭的子序列不會是最大子序列速蕊。
證明:令子序列為{ai, ..., aj}, 其中開頭的元素 ai < 0, 則 ai + ... + aj < ai+1+...+aj 顯然成立。

引理2:對子序列 {ai, ..., aj} , 如果該子序列滿足兩個條件:

  1. 如果對x取 [i, j) 中的任意整數(shù)(包含i,不包含j) sum{ai, ..., ax} >0.
  2. sum{ai, ..., aj}<0.

以該子序列中的任何元素ap開頭的以aj為終結(jié)的任意子序列的和必定小于0娘赴。

證明:從兩個條件中易推斷出:aj<0, 且由引理1知 以負數(shù)開頭的連續(xù)子序列不可能是最大連續(xù)子序列规哲,則: ai > 0.
顯然有 0 >= sum{ai, ..., aj} >= sum{ai-1, ..., aj} >= sum{ap, ..., aj}, 其中 p 是[i, j)之間的整數(shù)。
反證法:假設(shè)sum{ap, ..., aj}>0, p取 [i, j) 之間的整數(shù), 由引理2條件 sum{ai, ..., aj}<0 得出sum{ai, ..., ap-1}<0筝闹,該結(jié)論違反了引理2中的條件:如果對x取[i, j)中的任意整數(shù)(包含i,不包含j) sum{ai, ..., ax} >0. 得證媳叨。

由引理1可知,若a[i]<0, 則應(yīng)跳到a[i+1]作為子序列的開頭元素(如果a[i+1]>0);
由引理2可知关顷, 若a[i]+...+a[j]<=0且滿足引理2的第一個條件糊秆,則應(yīng)以a[j+1]作為最大連續(xù)子序列的開頭元素(如果a[j+1]>0). 實質(zhì)上,引理1是引理2的特例议双。

引理1和2可歸結(jié)為該狀態(tài)方程: maxsum(i)= max( maxsum(i-1)+ary(i), ary(i) ); (也可以由動態(tài)規(guī)劃方法處理的準則:最優(yōu)子結(jié)構(gòu)”痘番、“子問題重疊”、“邊界”和“子問題獨立”得到)
通過對給定序列順序地反復運用引理1和引理2平痰,最終可求得該序列的最大連續(xù)子序列汞舱。
代碼如下:

int maxSubSeq(int[] ary){
    int maxsum=0;
    int localSum=0;
    for (int i=0; i<ary.length; ++i){
        localSum += ary[i];
        if(localSum > maxsum){
            maxsum= localSum;
        }else if (localSum < 0){ 
            localSum=0; // 不考慮 ai~aj中的元素作為子序列的開頭, 其中ai>0, aj<0
        } //else  ==> localSum >0, 就是引理2中的條件1
    }
      return maxsum;
}

注意:解法2對于數(shù)組中全部是負數(shù)的數(shù)組返回0,而不是數(shù)組中的最大值宗雇。

解法4:動態(tài)規(guī)劃(可以處理數(shù)組中全部是負數(shù)的情況昂芜,該方法會返回數(shù)組中的最大值)

從解法2的分治思想得到提示,可以考慮數(shù)組的第一個元素A[0], 以及和最大的一段數(shù)組(A[i], .., A[j]), A[0] 和 和最大的一段數(shù)組的關(guān)系如下:

  1. 當0=i=j時,元素A[0]自己構(gòu)成和最大的一段赔蒲。
  2. 當0=i<j時泌神,元素和最大的一段數(shù)組以A[0]開頭A[j]結(jié)尾良漱。
  3. 當0 < i時,元素和和最大的一段數(shù)組沒有關(guān)系欢际。

因此母市,我們將一個大問題(具有N個元素的數(shù)組)轉(zhuǎn)換成較小的問題(具有N-1個元素的數(shù)組)

all[1] 為 A[1],...,A[N-1]中 和最大的一段數(shù)組之和
start[1] 為 A[1], ..., A[N-1]中 以A[1]開頭的和最大的一段數(shù)組之和

不難發(fā)現(xiàn),(A[0], A[1], ..., A[N-1]) 中和最大的一段數(shù)組的和 是 三種情況的最大值 max(A[0], A[0]+start[1], all[1])

可以看出該問題無后效性损趋,可以使用動態(tài)規(guī)劃的方案解決患久。

因此我們可以得到初始的算法:

 public static int maxSum1(int[] A){
        int[] start = new int[A.length];
        int[] all = new int[A.length];
        
        all[A.length-1] = A[A.length-1];
        start[A.length-1] = A[A.length - 1];
        for(int i = A.length-2; i>=0; --i){
            start[i] = Math.max(A[i], A[i] + start[i+1]);
            all[i] = Math.max(start[i], all[i+1]);
        }
        return all[0];
    }

算法優(yōu)化
可以看到,計算start[i] 時浑槽,和 start[i+1]有關(guān)蒋失,計算all[i] 時,和all[i+1]有關(guān)
因此括荡,我們可以使用兩個變量進行優(yōu)化高镐。

public static int maxSum1(int[] A){
       int nStart = A[A.length-1];
       int nAll = A[A.length-1];
        for(int i = A.length-2; i>=0; --i){
            nStart = Math.max(A[i], A[i] + nStart);
            nAll = Math.max(nStart , nAll);
        }
        return nAll;
    }

從上述優(yōu)化算法可以看出:當nStart < 0時溉旋,nStart被賦值為A[i].

因此我們可以將算法改寫為更清晰的寫法:

public static int maxSum(int[] A){
        int nStart = A[A.length-1];
        int nAll = A[A.length - 1];
        for(int i = A.length-2; i>=0; --i){
            if(nStart < 0){
                nStart = 0;
            }
            nStart += A[i];
            if(nStart > nAll){ /// 即使數(shù)組中全部是負數(shù)畸冲,我們也會選出具有最大值的數(shù)。
                nAll = nStart;
            }
        }
        return nAll;
    }

擴展問題

問題1

如果數(shù)組(A[0], ..., A[n-1])首尾相連观腊,即我們被允許找到一段數(shù)字(A[i],..., A[n-1], A[0], ..., A[j])式其和最大邑闲。

問題分解:

  1. 解沒有穿過A[n-1]和A[0]連接
  2. 解穿過了A[n-1]和A[0]連接
    2.1. 解包含A[0], ..., A[n-1]
    2.2. 解包含兩部分:(1)從A[0]開始的一段 (A[0], ..., A[j]) (0<=j <n); (2) 從A[i]開始的一段(A[i], .., A[n-1]) (j<i<n)

尋找2.2.的解 相當于 從A數(shù)組中刪除一塊子數(shù)組(A[j+1],....,A[i-1])且刪除的子數(shù)組的和是負數(shù)且其絕對值最大。這相當于將問題轉(zhuǎn)為子問題1梧油。

問題的解:取兩種情況的最大值苫耸。

時間復雜度 :求解子問題2只需遍歷數(shù)組一次,子問題1可以使用前面介紹的方法求解時間復雜度O(N). 所以時間復雜度共O(N)

代碼:(該代碼尚未驗證其正確性儡陨,請讀者自行驗證褪子,如有錯誤請留言評論)

/**
     * The correctness should be validated in the future!!!
     * @param A
     * @return
     */
    public static int maxSumCycle(int[] A){
        int s1 = maxSum(A);

        int s2 = 0;

        int nAll = A[A.length-2];
        int nStart = A[A.length-2];
        for(int i=A.length-1; i>=0; --i){
            s2 += A[i];

            // Find maximum abs value from range 1~A.length-2
            if(i>=1 && i<=A.length-3){
                nStart = Math.min(nStart, A[i] + nStart);
                nAll = Math.min(nStart, nAll);
            }

        }
        if(nAll>0) nAll = 0;
        return Math.max(s1, Math.max(s2, s2 + nAll));
    }

問題2

如果要求通知返回最大子數(shù)組的位置,應(yīng)該如何修改算法骗村,使保持O(N)的復雜度?

public static int maxSum(int[] A){

        int s=A.length-1, e=A.length-1; // [s, e]
        int p =0;
        int nStart = A[A.length-1];
        int nAll = A[A.length - 1];
        for(int i = A.length-2; i>=0; --i){
            if(nStart < 0){  // 以 A[i+1] 開頭的子數(shù)組的和嫌褪,不可能是最優(yōu)解,新的最優(yōu)解的終點應(yīng)該是 A[i]
                nStart = 0;
                p = i;
            }
            nStart += A[i];
            if(nStart > nAll){
                if(nStart==A[i]) e = p; //  表明以p為終點的最優(yōu)解 開始計算胚股。
                nAll = nStart;
                s = i; // 如果 nStart > nAll, 說明以當前 A[i] 開始一段數(shù)組笼痛,具有目前最優(yōu)的解。
            }
        }
        System.out.printf("sidx=%d, eidx=%d\n", s, e);
        return nAll;
    }

//測試實例琅拌,讀者可自行實驗缨伊,推導
//        int[] ary = {1, -2, 3, 10, -4, 7, 2, -5};
//        int[] ary = {0, -2, 3, 5, -1, 2};
//        int[] ary = {-9, -2, -3, -5, -3};
//        int[] ary = {1, -2, 3, 5, -3, 2};

注:解法4和擴展問題,都是引用《編程之美》上面的解法进宝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刻坊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子党晋,更是在濱河造成了極大的恐慌谭胚,老刑警劉巖活尊,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異漏益,居然都是意外死亡蛹锰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門绰疤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铜犬,“玉大人,你說我怎么就攤上這事轻庆⊙⒒” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵余爆,是天一觀的道長纷宇。 經(jīng)常有香客問我,道長蛾方,這世上最難降的妖魔是什么像捶? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮桩砰,結(jié)果婚禮上拓春,老公的妹妹穿的比我還像新娘。我一直安慰自己亚隅,他們只是感情好硼莽,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著煮纵,像睡著了一般懂鸵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上行疏,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天匆光,我揣著相機與錄音,去河邊找鬼隘擎。 笑死殴穴,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的货葬。 我是一名探鬼主播采幌,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼震桶!你這毒婦竟也來了休傍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蹲姐,失蹤者是張志新(化名)和其女友劉穎磨取,沒想到半個月后人柿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡忙厌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年凫岖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逢净。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡哥放,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出爹土,到底是詐尸還是另有隱情甥雕,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布胀茵,位于F島的核電站社露,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏琼娘。R本人自食惡果不足惜峭弟,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望轨奄。 院中可真熱鬧孟害,春花似錦、人聲如沸挪拟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玉组。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拉背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工桩蓉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毅访。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親潮孽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容