動態規劃(Dynamic Programming, DP)---- 最大連續子序列和 & 力扣53. 最大子序和
動態規劃(Dynamic Programming, DP)是一種用來解決一類最優化問題的演算法思想,簡單來使,動態規劃是將一個複雜的問題分解成若干個子問題,或者說若干個階段,下一個階段通過上一個階段的結果來推匯出,為了避免重複計算,必須把每階段的計算結果儲存下來,方便下次直接使用。
動態規劃有遞迴和遞推兩種寫法。一個問題必須擁有重疊子問題和最優子結構才能使用動態歸來來解決,即一定要能寫出一個狀態轉移方程才能使用動態規劃來解決。
最大連續子序列和:
令狀態dp[i]表示以A[i]作為末尾的連續序列的最大和,
思路一:動態規劃
程式碼:
1 // 最大連續子序列和
2 #include <stdio.h>
3 #include <algorithm>
4 using namespace std;
5
6 const int maxn = 10010;
7 int A[maxn], dp[maxn]; // A[i]存放序列,dp[i]存放以A[i]結尾的連續序列的最大和
8 int main()
9 {
10 freopen("in.txt", "r", stdin);
11 int n;
12 scanf("%d", &n);
13 for (int i = 0; i < n; i++){
14 scanf("%d", &A[i]);
15 }
16
17 // 邊界
18 dp[0] = A[0];
19 for (int i = 1; i < n; i++){
20 // 狀態轉移方程
21 dp[i] = max(dp[i - 1] + A[i], A[i]);
22 }
23
24 // dp[i]存放以A[i]結尾的連續序列的最大值,需要遍歷 i 得到最大的才是結果
25 int k = 0;
26 for (int i = 1; i < n; i++){
27 if (dp[i] > dp[k]){
28 k = i;
29 }
30 }
31
32 printf("%d\n", dp[k]);
33 fclose(stdin);
34 return 0;
35 }
Java程式碼實現:
1 class Solution { 2 public int maxSubArray(int[] nums) { 3 // 經典的動態規劃 4 int n = nums.length; 5 int[] dp = new int[n]; 6 dp[0] = nums[0]; 7 int max = dp[0]; 8 for(int i = 1; i < n; i++){ 9 dp[i] = Math.max(dp[i-1] + nums[i], nums[i]); 10 max = Math.max(dp[i], max); 11 } 12 13 return max; 14 } 15 }
力扣測試時間為:1ms, 空間為39.9MB
複雜度分析:
時間複雜度為O(n), 因為只遍歷了一次dp[]陣列
空間複雜度為O(n),因為額外需要一個dp[]陣列
思路二:貪心法
1. 遍歷所有元素,累加每個元素,如果sum<0, 重置sum=0, 重新開始查詢子序列
2.因為加上一個正數之前當前子序列和必定大於等於0,因為如果在加之前子序列和小於0的話在上一輪迭代的時候sum就已經被重置為0了,加上一個正數必定會使子序列和變大,加上一個負數可能會使子序列和暫時變小,如果這個負數太過分,使的子序列和小於0了,那就從下一個正數重新開始統計子序列,所以不用擔心子序列會被負數拖累而變小
1 class Solution {
2 public int maxSubArray(int[] nums) {
3 // 動態規劃,dp[i]表示以nums[i]為最後一個元素的子陣列的最大和
4 // dp[i] = dp[i-1] > 0 ? dp[i-1] + nums[i] : nums[i];
5 // dp[i]只和dp[i-1]有關,所以只需要一個臨時變數不斷更新dp[i-1]的值就行了,不需要一個數組dp[]
6 int len = nums.length;
7 int pre = nums[0];
8 int maxSum = pre, curSum = pre;
9 for(int i = 1; i < len; i++){
10 curSum = pre > 0 ? pre + nums[i] : nums[i];
11 maxSum = Math.max(maxSum, curSum);
12 pre = curSum;
13 }
14 return maxSum;
15 }
16 }
力扣測試時間為:1ms, 空間為39.6MB
複雜度分析:
時間複雜度為O(n), 因為只遍歷了一次陣列
空間複雜度為O(1)
思路三:分治法
1. 求左子區間的最大子序列和和右子區間的最大子序列和
2. 求當前區間包含nums[mid]和nums[mid+1]的最大子序列和,這一步又分為以下三步:
① 先求包含nums[mid]的左子區間的最大子序列和
② 再求包含nums[mid+1]的右子區間的最大子序列和
③ 將① 和② 的和相加就是我們要求的當前區間包含nums[mid]和nums[mid+1]的最大子序列和
3. 返回左右中的最大子序列和中的較大者
1 class Solution {
2 public int maxSubArray(int[] nums) {
3 // 分治法
4 return helpMaxSubArray(nums, 0, nums.length - 1);
5 }
6
7 public int helpMaxSubArray(int[] nums, int left, int right){
8 // 如果只有一個元素,直接返回這個元素即可
9 if(left == right){
10 return nums[left];
11 }
12 // 遞迴求左右子區間的最大子序列和
13 int mid = (right - left) / 2 + left;
14 int leftMaxSum = helpMaxSubArray(nums, left, mid);
15 int rightMaxSum = helpMaxSubArray(nums, mid + 1, right);
16
17 // 求出包含nums[mid]的最大子序列和 midLeftMaxSum
18 int midLeftMaxSum = nums[mid];
19 int sum = 0;
20 for(int i = mid; i >= left; i--){
21 sum += nums[i];
22 midLeftMaxSum = Math.max(sum, midLeftMaxSum);
23 }
24
25 // 求出包含nums[mid+1]的最大子序列和midRightMaxSum
26 int midRightMaxSum = nums[mid+1];
27 sum = 0;
28 for(int i = mid + 1; i <= right; i++){
29 sum += nums[i];
30 midRightMaxSum = Math.max(sum, midRightMaxSum);
31 }
32
33 // midLeftMaxSum + midRightMaxSum即是包含了nums[mid]和nums[mid+1]的最大子序列和
34 int midMaxSum = midLeftMaxSum + midRightMaxSum;
35
36 // 三個最大和進行比較,返回最大的一個
37 return Math.max(Math.max(leftMaxSum, rightMaxSum), midMaxSum);
38 }
39 }
力扣測試時間為1ms, 39.8MB
複雜度分析:
時間複雜度:對於每個[left, right]區間都會遍歷2遍,第一次是分別求出左右區間的最大子序列和,第二次是求出包含中間元素的最大子序列和,一共有logn層,所以時間複雜度為O(nlogn)
空間複雜度:取決於遞迴棧的層數,為logn,所以空間複雜度為O(logn)
題型實戰:
1007 Maximum Subsequence Sum (25分)Given a sequence of K integers { N1, N2, ..., NK }. A continuous subsequence is defined to be { Ni, Ni+1, ..., Nj } where 1≤i≤j≤K. The Maximum Subsequence is the continuous subsequence which has the largest sum of its elements. For example, given sequence { -2, 11, -4, 13, -5, -2 }, its maximum subsequence is { 11, -4, 13 } with the largest sum being 20.
Now you are supposed to find the largest sum, together with the first and the last numbers of the maximum subsequence.
Input Specification:
Each input file contains one test case. Each case occupies two lines. The first line contains a positive integer K (≤10000). The second line contains K numbers, separated by a space.
Output Specification:
For each test case, output in one line the largest sum, together with the first and the last numbers of the maximum subsequence. The numbers must be separated by one space, but there must be no extra space at the end of a line. In case that the maximum subsequence is not unique, output the one with the smallest indices i and j (as shown by the sample case). If all the K numbers are negative, then its maximum sum is defined to be 0, and you are supposed to output the first and the last numbers of the whole sequence.
Sample Input:
10
-10 1 2 3 4 -5 -23 3 7 -21
Sample Output:
10 1 4
程式碼:
1 #include <stdio.h>
2 #include <algorithm>
3 using namespace std;
4
5 const int maxn = 10010;
6
7 int A[maxn];
8 // 兩個陣列
9 int firsts[maxn], dp[maxn];
10
11 int main()
12 {
13 // 讀入資料
14 // freopen("in.txt", "r", stdin);
15 int n;
16 scanf("%d", &n);
17 for (int i = 0; i < n; i++){
18 scanf("%d", &A[i]);
19 }
20
21 // 邊界
22 dp[0] = A[0], firsts[0] = 0;
23 // 如果d[i - 1] > 0, firsts[i] = first[i - 1], 否則firsts[i] = i;
24 for (int i = 1; i < n; i++){
25 if (dp[i - 1] >= 0){
26 firsts[i] = firsts[i - 1];
27 dp[i] = dp[i - 1] + A[i];
28 }
29 else{
30 firsts[i] = i;
31 dp[i] = A[i];
32 }
33 }
34 // 遍歷dp,尋找最大的子序列和
35 int k = 0;
36 for (int i = 1; i < n; i++){
37 if (dp[i] > dp[k]){
38 k = i;
39 }
40 }
41
42 // 輸出
43 if (dp[k] < 0){ // 如果最大子序列和都小於0,說明所有元素都是負數
44 printf("0 %d %d\n", A[0], A[n - 1]);
45 }
46 else{
47 printf("%d %d %d\n", dp[k], A[firsts[k]], A[k]);
48 }
49
50 // fclose(stdin);
51
52 return 0;
53 }
思路參考:
https://leetcode-cn.com/problems/maximum-subarray/solution/zui-da-zi-xu-he-by-leetcode-solution/