摘要
上期介绍动态规划的什么,有什么特性,能解决什么类型的问题。这期就通过一个案例,看一下动态规划解决问题的过程。
动态规划解决问题,需要做好这三步,1 定义状态,2 梳理状态转换方程,3 确定状态初始状态,最后得出的结果值。
接下来通过求最多子序列和这个案例,来体会一下。
最多子序列和
给定一个长度为 n 的整数序列,求它的最大连续子序列和,比如 -2、1、-3、4、-1、2、1、-5、4 的最大连续子序列和是 4 + (-1) + 2 + 1 =6。
首先定义状态,假设 dp(i)
是以 nums[i]
结尾的最大子序列和(nums
是整个序列):
- 以 nums[0] = -2 结尾的最大子序列是 -2,所以 dp(0) = -2;
- 以 nums[1] = 1 结尾的最大子序列是 1,所以 dp(1) = 1;
- 以 nums[2] = -3 结尾的最大子序列是 1、-3,所以 dp(2) = dp(1) -3 = -2;
- 以 nums[3] = 4 结尾的最大子序列是 4,所以 dp(3) = 4;
- 以 nums[4] = -1 结尾的最大子序列是 4、-1,所以 dp(4) = dp(3) - 1 = 3;
- 以 nums[5] = 2 结尾的最大子序列是 4、-1、2,所以 dp(5) = dp(4) + 2 = 5;
- 以 nums[6] = 1 结尾的最大子序列是 4、-1、2、1,所以 dp(6) = dp(5) + 1 = 6;
- 以 nums[7] = -5 结尾的最大子序列是 4、-1、2、1、-5,所以 dp(7) = dp(6) - 5 = 1;
- 以 nums[8] = 5 结尾的最大子序列是 4、-1、2、1、-5、4,所以 dp(8) = dp(7) + 4 = 5;
注意:
定义状态部分中“以 xx 结尾”,就是这个连续子序列必须以 xx 结尾。
然后梳理总结出状态转移方程,即总结出规律:
- 如果 dp(i-1) ≤ \leq ≤ 0,那么 dp(i) = nums[i];
- 如果 dp(i-1) >` 0, 那么 dp(i) = dp(i-1) + nums[i]。
然后确定初始状态,dp(0) = nums[0]
。
最后,就可以得出最大子序列和是所以 dp(i)
中的最大值 max { dp(i) }, i ∈ \in ∈ [0, nums.length]。
实现代码
按照上面的梳理,实现代码如下:
static int maxSubArray1(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int[] dp = new int[nums.length];
dp[0] = nums[0];
int max = dp[0];
for (int i = 1; i < nums.length; i++) {
int prev = dp[i-1];
if (prev <= 0) {
dp[i] = nums[i];
} else {
dp[i] = prev + nums[i];
}
max = Math.max(dp[i], max);
}
return max;
}
实现代码的空间复杂度和时间复杂度都是 O(n)。
看代码逻辑,每次 dp(i)
的处理只和 dp(i-1)
有关,那么就可以存在一个变量记录 dp(i-1)
的值,不需要一个 dp 数组去处理。优化代码如下:
static int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int dp = nums[0];
int max = dp;
for (int i = 1; i < nums.length; i++) {
int prev = dp;
if (prev <= 0) {
dp = nums[i];
} else {
dp = prev + nums[i];
}
max = Math.max(dp, max);
}
return max;
}
优化后的时间复杂度是 O(n),但是空间复杂度降为 O(1) 了。
总结
1、 定义状态;
2、 梳理状态转换方程;
3、 确定状态初始状态;
这就是动态规划的正确解决套路。
版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: