动态规划理论基础
动态规划的题目由重叠子问题构成,每一个状态一定是由上一个状态推导出来的。
动态规划五部曲
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
动态规划里面递推公式十分重要,但是确定dp数组,初始化,遍历顺序也同样十分重要,一定要严格按照这五步进行,将每一步的思路理清。
当做动态规划的题目遇到问题时,最好的方式就是打印出dp数组,看是否和自己推理的一样。
509. 斐波那契数
思路:动态规划五步曲:
- dp[i]代表第i个数的斐波那契数是多少。
- 递推公式:dp[i] = dp[i - 1] + dp[i - 2]
- 初始化dp[0] = 0, dp[1] = 1
- 从递推公式可以看出,一定是从前向后遍历的。
- 举例看是否可行
class Solution {
public int fib(int n) { // 动态规划
if (n == 0) return 0;
if (n == 1) return 1;
int dp0 = 0;
int dp1 = 1;
for (int i = 2; i <= n; i++) {
int temp = dp1;
dp1 += dp0;
dp0 = temp;
}
return dp1;
}
}
70. 爬楼梯
思路:动态规划五部曲
- dp[i]表示爬到第i层可以有几种方式
- 递推公式:dp[i] = dp[i - 1] + dp[i - 2](推导过程参考随想录)
- 初始化dp[1] = 1, dp[2] = 2
- 从递推公式可以看出是从前向后遍历。
- 举例看是否可行
class Solution {
public int climbStairs(int n) {
if (n == 1) return 1;
// 1、确定dp数组及下标含义
// dp[i]代表当前阶有几种方法
int[] dp = new int[n + 1];
// 2、确定递推函数
// dp[i] = dp[i - 1] + (i) - 2
// 3、确定初始化
dp[1] = 1;
dp[2] = 2;
// 4、确定遍历顺序
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
本题与斐波那契相同,可以降低空间复杂度。
746. 使用最小花费爬楼梯
思路:动态规划五步曲
- dp[i]代表到达第i个台阶所花费的最小体力(这里认为第一步无需支付费用)
- 递推公式:dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]),因为到达第i个台阶的途径有两种,从dp[i - 1]或者dp[i - 2],比较从这两个过来哪一个花费更小即可。
- 初始化,dp[0] = 0, dp[1] = 0。我们认为第一步无需支付费用,所以到第一个台阶和到第二个台阶都是0。
- 遍历顺序为从前到后
- 举例是否符合
class Solution {
public int minCostClimbingStairs(int[] cost) {
int len = cost.length;
int[] dp = new int[len + 1];
// 每次最多走两步,前两个台阶无需支付费用
dp[0] = 0;
dp[1] = 0;
// 计算到达每一层台阶的最小费用
for (int i = 2; i <= len; i++) {
dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[len];
}
}