1. 关于动规
- dp[ ]数组思维
- dp[ ]用来储存的是情况为(参数输入为)n时的值,和上一个值紧密相关(并不一定是n-1,也可能是n-2,…)。要先发现n和上一个值的关系,以及如何优化它们两步之间的关系
- 将每一个子问题的答案都储存在dp[ ]里,可以通过i++的遍历来访问每一种条件下的可能性
- 考虑之前累积子问题的最小花费,而不是当前选项的最小花费(贪心思维)
动规的应用场景
待施工
2. 例题
lc70 爬楼梯
易错点
- 为什么不用递归:
注意:因为递归的思路是基于分治子问题,时间复杂度O(2^n),在碰到比较大的n就会超时,所以不能用递归,而是通过每次存储子问题结果来避免重复二分计算。这也是为什么会提到dp是个数组 - dp[2]=2 -->如果n=1? 要给n==1专门开if支线避免数组越界
- int[] dp=new int[length+1]; 注意这里的n+1,是因为每个索引是从0开始的,但考虑到数组下标与数字错位一格的情况,这里设置为len+1
思路
- 如何得出dp数组?
- 从上一步到第n个台阶,有那些可能? 根据每次可以迈1~2步,答案是第n-1和第n-2个台阶处,故每个第n个台阶的方法数=后二者之和
- 将这些答案都储存在一个dp数组中,通过索引下标的方式来获取n格时的答案
- 注意,这个数组在n=1的时候就会因为数组越界报错,所以要设置if支线
- 递推公式:dp[i]=dp[i-2]+dp[i-1]
- 初始化:f[1]=1,f[2]=2
- 遍历顺序:从前到后
代码实现
class Solution {
public int climbStairs(int n) {
注意,要为n=1专门开一个if支线,因为这个时候dp[2]会报错
if(n==1){
return 1;
}
//用一个临时数组来储存子问题的结果,避免重复的二分计算;
int[] dp=new int[n+1];
//注意这里的n+1,是因为每个索引是从0开始的,所以dp[1]其实是数组里的第二个数,dp[n]是第n+1个数,但是为了理解方便,这里是从dp[1]开始初始化的
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
dp[i]=dp[i-2]+dp[i-1];
}
return dp[n];
}
}
lc746 使用最小花费爬楼梯
思路
- dp数组:从上一步到第i阶,最小的花费是多少?(这也是为什么可以用i++一格一格地遍历数组,而不需要考虑是i+1还是i+2,因为储存的是不同台阶数量的最小花费,而不是固定台阶数量时的花费策略
- 递推公式:min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
- 初始化:第一步不用钱,即0
- 遍历顺序:从前到后
易错点
注意遍历dp[i]时的终止位置
代码实现
class Solution {
public int minCostClimbingStairs(int[] cost) {
int[] dp=new int[cost.length+1];
//初始值0或1:不花费用
dp[0]=0;
dp[1]=0;
for(int i=2;i<=cost.length;i++){ //易错点1:注意是i<=len,因为最后一步是要跳出数组,从cost[length](which事实上不存在会越界)开始往后倒退1-2格
dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
return dp[cost.length];
}
}