1. 程式人生 > >(DP)最大子段和問題分析和總結(…

(DP)最大子段和問題分析和總結(…

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

一.問題描述

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

二. 問題分析

1.窮舉法

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

?
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 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).程式碼如下:

?
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 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是第三種情況可能的最大值

參考程式碼如下:

?
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 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]為最優解矛盾.

參考程式碼如下:

?
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 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),是最優的解,這裡推薦練習一下UVA507來加深理解. 我以前的題解:

相關推薦

DP大子問題分析總結

最大子段和問題(Maximum Interval Sum) 經典的動態規劃問題,幾乎所有的演算法教材都會提到.本文將分析最大子段和問題的幾種不同效率的解法,以及最大子段和問題的擴充套件和運用. 一.問題描述 給定長度為n的整數序列,a[1...n], 求[1,n]某個子區間[i , j]使得a[i]+…+a

hdu1231 大連續子序列DP大子序列

和上一題一樣,只不過變為陣列。 #include <stdio.h> #include <string.h> #include <algorithm> using

280D k-Maximum Subsequence Sum區間大k線段樹 + 大子 + 區間修改 + 區間查詢 + 單點修改

題目 題意 給定nn個數的序列,定義兩個操作 ⋅0kval⋅0kval 把序列第k個數的值變為val ⋅1lrk⋅1lrk 詢問在區間 Al⋯ArAl⋯Ar 中,選取 mm 段不相交的子區間,使得這 mm 段子區間的和最大,其中0≤m≤k0≤m≤k。

Leetcode題解之設計問題2大子小棧

題目:https://leetcode-cn.com/explore/interview/card/top-interview-questions-easy/24/design/59/ 題目:  最小棧 設計一個支援 push,pop,top 操作,並能在常數時間內檢索到最小元

Leetcode題解之動態規劃4大子打家劫舍

題目:https://leetcode-cn.com/explore/interview/card/top-interview-questions-easy/23/dynamic-programming/57/ 題目描述: 打家劫舍 你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有

Leetcode題解之動態規劃3大子

題目:https://leetcode-cn.com/explore/interview/card/top-interview-questions-easy/23/dynamic-programming/56/ 題目描述:   最大子序和 給定一個整數陣列 

leetcode3大子的js實現

一.題目描述: 給定一個整數陣列 nums ,找到一個具有最大和的連續子陣列(子陣列最少包含一個元素),返回其最大和。 示例: 輸入: [-2,1,-3,4,-1,2,1,-5,4], 輸出: 6 解釋: 連續子陣列 [4,-1,2,1] 的和最大,為 6 二.js程式碼實現

動態規劃法大子陣列問題maximum subarray problem

問題簡介   本文將介紹計算機演算法中的經典問題——最大子陣列問題(maximum subarray problem)。所謂的最大子陣列問題,指的是:給定一個數組A,尋找A的和最大的非空連續子陣列。比如,陣列 A = [-2, -3, 4, -1, -2, 1

圖論演算法--短路徑的DFS/BFS解法JAVA

最短路徑–城市路徑問題: 問題描述:求從1號城市到5號城市的最短路徑長度 Input: 5 8 1 2 2 1 5 10 2 3 3 2 5 7 3 1 4 3 4 4

51Nod 1050 迴圈陣列大子dp

 其實就是求迴圈陣列的最大欄位和 題意:給定一個長度為50000的陣列,求它的迴圈陣列的最大子段和。 分析:本題與普通的最大子段和問題不同的是,最大子段和可以是首尾相接的情況,即可以迴圈。那麼

『嗨威說』演算法設計與分析 - PTA 數字三角形 / 大子 / 編輯距離問題第三章上機實踐報告

本文索引目錄: 一、PTA實驗報告題1 : 數字三角形   1.1  實踐題目   1.2  問題描述   1.3  演算法描述   1.4  演算法時間及空間複雜度分析 二、PTA實驗報告題2 : 最大子段和   2.1  實踐題目   2.2  問題描述   2.

C++學習1大子多種解法

多少 問題: code namespace 數據 組成 amp using () 問題:給定由n個數(可能為負數)組成的序列a1,a2,a3,...,an,求該序列子段和的最大值。 第一種解法:(最容易考慮的方法,將所有的子段一一相加,然後比較) 1 #include&

SP1043 GSS1 - Can you answer these queries I線段樹,區間大子靜態

有一種 nbsp 不用 端點 合並 表示 格式 space iostream 題目描述 給出了序列A[1],A[2],…,A[N]。 (a[i]≤15007,1≤N≤50000)。查詢定義如下: 查詢(x,y)=max{a[i]+a[i+1

codevs 3981 動態大子線段樹

輸入 typedef fault namespace 一行 scrip img sum spl 題目傳送門:codevs 3981 動態最大子段和 題目描述 Description 題目還是簡單一點好... 有n個數,a[1]到a[n]。 接下來q次查詢,每次動

[SHOI2015]腦洞治療儀惡心的線段樹,區間大子

由於 得到 \n define 範圍 ret scan 定義 add 題目描述: 曾經發明了自動刷題機的發明家 SHTSC 又公開了他的新發明:腦洞治療儀——一種可以治療他因為發明而日益增大的腦洞的神秘裝置。 為了簡單起見,我們將大腦視作一個 01 序列。11代表這個位置的

【SHOI2015】腦洞治療儀惡心的線段樹,區間大子

-i string 修改 def 由於 返回 系列 lazy long 題目描述: 曾經發明了自動刷題機的發明家 SHTSC 又公開了他的新發明:腦洞治療儀——一種可以治療他因為發明而日益增大的腦洞的神秘裝置。為了簡單起見,我們將大腦視作一個 01

51Nod 1051 - 大子矩陣DP

題目連結 http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1051 【題目描述】 一個M*N的矩陣,找到此矩陣的一個子矩陣,並且這個子矩陣的元素的和是最大的,輸出這個最大的值。 例如:3×3的矩陣: -1 3

51Nod 1052 - 大M子DP

題目連結 http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1052 【題目描述】 N個整陣列成的序列a[1],a[2],a[3],…,a[n],將這N個數劃分為互不相交的M個子段,並且這M個子段的和是最大的。如果

51Nod1050 迴圈陣列大子動態規劃

這題區間是可以迴圈的,如果不迴圈的狀態轉移方程是 if(dp[i-1]>0)   dp[i]=dp[i-1]+a[i]; else   dp[i]=a[i]; 現在題目要求是可以迴圈,分為兩種情況: 1、沒有迴圈,找到了最大的子段。 2、迴圈了,找到了最大的子段。 第一

大子長遞增子序列貪心與動態規劃

話不多說先上程式碼。。。。。  最大子段和 題目描述 給出一段序列,選出其中連續且非空的一段使得這段和最大。 輸入輸出格式 輸入格式:   第一行是一個正整數NNN,表示了序列的長度。 第二行包含NNN個絕對值不大於100001000010000的