动态规划(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)
题型实战:
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/