动态规划算法刷题记录
动态规划
简单介绍
动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
所以动态规划中每一个状态一定是由上一个状态推导出来的。这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。【动规是由前一个状态推导出来的,而贪心是局部直接选最优的】
所以贪心解决不了动态规划的问题
解题思路
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化【根据第2步判断】
- 确定遍历顺序
- 举例推导dp数组
一维入门
【简单】509. 斐波那契数
分析:当前状态由前两个状态推出,可以使用动规
五步走:
- 确定dp数组(dp table)以及下标的含义:dp[i]表示第i个数字为多少
- 确定递推公式:
dp[i] = dp[i-1] + dp[i-2]
- dp数组如何初始化
- 根据上一步进行初始化
- 注意题目也给了:dp[0] = 0,dp[1] = 1
- 确定遍历顺序:dp[i] = dp[i-1] + dp[i-2]可知从前往后遍历
- 举例推导dp数组
class Solution { |
⭐优化:我们只在乎dp[i] 、dp[i-1]、dp[i-2]三个数字,而不需要记录整个序列
class Solution { |
【简单】70. 爬楼梯
分析:主要是由每次可以爬1/2层推出递归公式 + 初始化从1开始
五步走:
- 确定dp数组(dp table)以及下标的含义:dp[i]表示第i层楼梯有多少种到达方式
- 确定递推公式:
dp[i] = dp[i-1] + dp[i-2]
- dp数组如何初始化
- 根据上一步进行初始化
- 注意题目也给了:dp[1] = 1,dp[2] = 2
- 确定遍历顺序:dp[i] = dp[i-1] + dp[i-2]可知从前往后遍历
- 举例推导dp数组
class Solution { |
同样可以优化空间,方法同509
【简单】746. 使用最小花费爬楼梯
分析:看起来涉及到两个维度:阶梯数&体力,但是这两个维度是联系的(也就是阶梯数对应体力)
五步走:
- 确定dp数组(dp table)以及下标的含义:dp[i]表示到达第i层花费的最小体力
- 确定递推公式:
dp[i] = min(dp[i-1] + cost[i-1] , dp[i-2] + cost[i-2])
,也计算就是从第i-1层还是第i-2层跳到第i层花费体力小 - dp数组如何初始化
- 你可以选择从下标为
0
或下标为1
的台阶开始爬楼梯:dp[0] = 0,dp[1] = 0
- 你可以选择从下标为
- 确定遍历顺序:由递推公式可知从前往后遍历
- 举例推导dp数组
class Solution { |
同样可以优化空间,方法同509
二维入门
【中等】62.不同路径
分析:当前点位置路径数 = 左侧位置路径数+上侧位置路径数
五步走:
- 确定dp数组(dp table)以及下标的含义:dp[i] [j]表示到达(i,j)的路径有多少条
- 确定递推公式:
dp[i] [j] = dp[i] [j-1] +dp[i-1] [j]
- dp数组如何初始化
- dp[0] [0] = 0
- 第一行和第一列都是1
- 确定遍历顺序:由递推公式可知从前往后遍历
- 举例推导dp数组
public int ( m, int n) { |
优化:状态压缩
// [优化]:关于对角线对称,本质上就是杨辉三角 |
【中等】63. 不同路径 II
分析:总体思路同上,遇到障碍物把当前路径数设为0就好了
五步走:
- 确定dp数组(dp table)以及下标的含义:dp[i] [j]表示到达(i,j)的路径有多少条
- 确定递推公式:
dp[i] [j] = dp[i] [j-1] +dp[i-1] [j];
if(obstacleGrid[i] [j] == 0) dp[i] [j] = 0
- dp数组如何初始化
- dp[0] [0] = 0
- 第一行和第一列都是1
- 确定遍历顺序:由递推公式可知从前往后遍历
- 举例推导dp数组
class Solution { |
优化:思路同62
顺带嘲笑一下脑子没转过弯来的自己
【中等】 343. 整数拆分
分析:一开始会一直纠结到底是分成两个数还是几个数,我们统一分成两个数
dp[i]有两种得到方式:
直接拆分:j * (i - j) 【真正意义上的拆分成两个数】
继续拆分:j * dp[i - j] 【这个就相当于把i-j再进行拆分,也就包括了多个数的情况】
五步走:
- 确定dp数组(dp table)以及下标的含义:dp[i] 表示i可以被拆分的最大乘积
- 确定递推公式:
dp[i] = max(dp[i] , max((i-j) * j , dp[i-j] * j))
- 两层max:里面的max是比较直接拆分和继续拆分的大小,外面的是把当前dp[i]和新的计算结果相比
- dp数组如何初始化:dp[2] = 1,0和1没有意义
- 确定遍历顺序:双层遍历
- 举例推导dp数组
class Solution { |
【中等】 96.不同的二叉搜索树
分析:额有点数学题,难度可以算半个困难了【推导过程见代码随想录 (programmercarl.com)】
反正最后可以推出dp[i] += dp[j - 1] * dp[i - j],j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量
五步走:
- 确定dp数组(dp table)以及下标的含义: 1到i为节点组成的二叉搜索树的个数为dp[i]
- 确定递推公式:
dp[i] += dp[j - 1] * dp[i - j];
- j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量
- dp数组如何初始化:dp[0] = 1
- 确定遍历顺序:遍历i里面每一个数作为头结点的状态,用j来遍历。
- 举例推导dp数组
class Solution { |
背包问题
对于面试,只需要掌握到01背包和完全背包就够了
01 背包
题目:有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
分析:
五步走:
确定dp数组(dp table)以及下标的含义: 即dp[i] [j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
确定递推公式:
dp[i] [j] = max(dp[i - 1] [j], dp[i - 1] [j - weight[i]] + value[i]);
- 不放物品i:也就是dp[i - 1] [j],即背包容量为j,里面不放物品i的最大价值,所以背包内的价值依然和前面相同。
- 放物品i:也就是dp[i - 1] [j - weight[i]] + value[i] 推出
dp[i - 1] [j - weight[i]]
为背包容量为j - weight[i]
的时候不放物品i的最大价值- 那么
dp[i - 1] [j - weight[i]]
+value[i]
,就是背包放物品i得到的最大价值
dp数组如何初始化:
首先从dp[i] [j]的定义出发,如果背包容量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数组