动态规划理论基础
所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的.
对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
一些同学可能想为什么要先确定递推公式,然后在考虑初始化呢?
因为一些情况是递推公式决定了dp数组要如何初始化!
做动规的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,确定最后推出的是想要的结果。
然后再写代码,如果代码没通过就打印dp数组,看看是不是和自己预先推导的哪里不一样。
如果打印出来和自己预先模拟推导是一样的,那么就是自己的递归公式、初始化或者遍历顺序有问题了。
如果和自己预先模拟推导的不一样,那么就是代码实现细节有问题。
这样才是一个完整的思考过程,而不是一旦代码出问题,就毫无头绪的东改改西改改,最后过不了,或者说是稀里糊涂的过了。
509. 斐波那契数
代码实现
class Solution {
public int fib(int n) {
int[] dp=new int[n+1];
if(n<=1){return n;}
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
70. 爬楼梯
代码实现
dp[n]只能是在dp[n-1]基础上走一个台阶,或者在dp[n-2]基础上走两个台阶
class Solution {
public int climbStairs(int n) {
int[] dp=new int[n+1];//dp[n]指的是到达台阶n有几种方案
//初始化
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
746. 使用最小花费爬楼梯
这道题我的递推公式理解错误了,初始版本同时产生cost[i[数组溢出的问题
class Solution {
public int minCostClimbingStairs(int[] cost) {
//dp[i]表示到达台阶i花费了多少钱
int[] dp=new int[cost.length+1];
dp[0]=cost[0];
dp[1]=cost[1];
for(int i=2;i<dp.length;i++){
System.out.println(i);
dp[i]=Math.min(dp[i-1]+cost[i],dp[i-2]+cost[i]);
}
return dp[cost.length];
}
}
重新审题:
跳到 下标 0 或者 下标 1 是不花费体力的, 从 下标 0 下标1 开始跳就要花费体力了。
dp[i]的定义:到达第i台阶所花费的最少体力为dp[i]。
可以有两个途径得到dp[i],一个是dp[i-1] 一个是dp[i-2]。
dp[i - 1] 跳到 dp[i] 需要花费 dp[i - 1] + cost[i - 1]。
dp[i - 2] 跳到 dp[i] 需要花费 dp[i - 2] + cost[i - 2]。
代码实现
class Solution {
public int minCostClimbingStairs(int[] cost) {
//dp[i]表示到达台阶i花费了多少钱
int[] dp=new int[cost.length+1];
//初始化为0是因为题目给出从1,2台阶开始跳,那么从1,2台阶开始
dp[0]=0;
dp[1]=0;
for(int i=2;i<dp.length;i++){
// System.out.println(i);
dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
return dp[cost.length];
}
}