最大連續子序列和問題如下:
下面介紹動態規划的做法,復雜度為 O(n)。
步驟 1:令狀態 dp[i] 表示以 A[i] 作為末尾的連續序列的最大和(這里是說 A[i] 必須作為連續序列的末尾)。
步驟 2:做如下考慮:因為 dp[i] 要求是必須以 A[i] 結尾的連續序列,那么只有兩種情況:
-
- 這個最大和的連續序列只有一個元素,即以 A[i] 開始,以 A[i] 結尾。
- 這個最大和的連續序列有多個元素,即從前面某處 A[p] 開始 (p<i),一直到 A[i] 結尾。
對第一種情況,最大和就是 A[i] 本身。
對第二種情況,最大和是 dp[i-1]+A[i]。
於是得到狀態轉移方程:
dp[i] = max{A[i], dp[i-1]+A[i]}
這個式子只和 i 與 i 之前的元素有關,且邊界為 dp[0] = A[0],由此從小到大枚舉 i,即可得到整個 dp 數組。接着輸出 dp[0],dp[1],...,dp[n-1] 中的最大子即為最大連續子序列的和。
代碼如下:
1 /* 2 最大連續子序列和 3 */ 4 5 #include <stdio.h> 6 #include <string.h> 7 #include <math.h> 8 #include <stdlib.h> 9 #include <time.h> 10 #include <stdbool.h> 11 12 #define maxn 10010 13 int A[maxn], dp[maxn]; // A[i] 存放序列,dp[i] 存放以 A[i] 為結尾的連續序列的最大和 14 15 // 求較大值 16 int max(int a, int b) { 17 return a>b ? a : b; 18 } 19 20 int main() { 21 int n, i, k; 22 scanf("%d", &n); 23 for(i=0; i<n; ++i) { // 輸入序列 24 scanf("%d", &A[i]); 25 } 26 dp[0] = A[0]; // 邊界 27 for(i=1; i<n; ++i) { 28 // 狀態轉移方程 29 dp[i] = max(A[i], dp[i-1] + A[i]); 30 } 31 // 求最大連續子序列和 32 k = dp[0]; 33 for(i=1; i<n; ++i) { 34 if(dp[i] > k) { 35 k = dp[i]; 36 } 37 } 38 printf("%d\n", k); // 輸出 39 40 return 0; 41 }
此處順便介紹無后效性的概念。狀態的無后效性是指:當前狀態記錄了歷史信息,一旦當前狀態確定,就不會再改變,且未來的決策只能在已有的一個或若干個狀態的基礎上進行,歷史信息只能通過已有的狀態去影響未來的決策。針對本節問題來說,每次計算狀態 dp[i],都只會涉及 dp[i-1],而不直接用到 dp[i-1] 蘊含的歷史消息。