leetcode动态规划题解
本文是基本常见的动态规划题解
一、动态规划简介
0-1背包理论基础(一)
动规五部曲分析
- 确定dp数组以及下标的含义
那么这里 i 、j、dp[i][j] 分别表示什么呢?
i 来表示物品、j表示背包容量。
即****dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少****。
- 确定递推公式
以上过程,抽象化如下:
- ****不放物品i****:背包容量为j,里面不放物品i的最大价值是dp[i - 1][j]。
- ****放物品i****:背包空出物品i的容量后,背包容量为j - weight[i],dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]且不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值
递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
- dp数组如何初始化
****关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱****。
如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。
状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。
dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。
那么很明显当 j < weight[0]
的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。
当j >= weight[0]
时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。
dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢?
其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出dp[i][j] 是由左上方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖。
***初始-1,初始-2,初始100,都可以!*****
但只不过一开始就统一把dp数组统一初始为0,更方便一些
最后初始化代码如下:
1 | // 初始化 dp |
- 确定遍历顺序
那么问题来了,***先遍历 物品还是先遍历背包重量呢?*****
****其实都可以!! 但是先遍历物品更好理解******。
为什么也是可以的呢?
****要理解递归的本质和递推的方向******。
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
递归公式中可以看出dp[i][j]是靠dp[i-1][j]和dp[i - 1][j - weight[i]]推导出来的。
dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正上方向)
***大家可以看出,虽然两个for循环遍历的次序不同,但是dp[i][j]所需要的数据就是左上角,根本不影响dp[i][j]公式的推导!*****
但先遍历物品再遍历背包这个顺序更好理解。
***其实背包问题里,两个for循环的先后循序是非常有讲究的,理解遍历顺序其实比理解推导公式难多了*****
- 举例推导dp数组
手动模拟一下
模板代码
1 |
|
二、动态规划题解
300. 最长递增子序列
又叫做最长上升子序列
子序列一般是如下定义
“子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序”。
子串一般是连续的
当然也有特例, 不过特例题干都会明确标出来.例如说 连续的 子序列
- dp[i]的定义
***dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度*****
- 状态转移方程
位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。
所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
****注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值******。
- dp[i]的初始化
每一个i,对应的dp[i](即最长递增子序列)起始大小至少都是1.
- 确定遍历顺序
dp[i] 是有0到i-1各个位置的最长递增子序列 推导而来,那么遍历i一定是从前向后遍历。
j其实就是遍历0到i-1,那么是从前到后,还是从后到前遍历都无所谓,只要吧 0 到 i-1 的元素都遍历了就行了。 所以默认习惯 从前向后遍历。
C++代码如下:
1 | class Solution { |
待更新……