力扣打卡:746. 使用最小花费爬楼梯
解题思路
动态规划的题目还是要写出暴力递归的方法后再去优化
流程
暴力递归最关键的是怎么找到状态转移的方程
- 状态转移的方程也就是当前的状态和子问题状态之间的联系
- 怎么得到子问题和当前状态之间的联系呢? 需要用到暴力递归的函数
- 暴力递归的函数就是求出每一个问题的解,子问题的解
- 如斐波那契数列,需要两个数的和,那么定义的函数就是求出了前第一个和前第二个函数
- 不要想着自己定义的函数能否求出来
- 每次写递归函数: 直接调用函数就就可以得到需要的答案
- 这样写递归函数的时候才不会出现牛头不对马嘴的情况,比如乱定义参数,乱定义返回值等
- 在函数中写出当前状态和子问题后就考虑返回的是什么了
自顶向下
写出了暴力递归的方法,多增加 判断 和 记录 就是自顶向下的动态规划了
代码
class Solution {
public int minCostClimbingStairs(int[] cost) {
// 在分析暴力递归时,抓住每一个节点,或者是最通用的情况
// 每个节点的最优体力是什么,肯定是前两个中最小的元素加上本身
// 题目所给的,我们要分析出当前的对象是谁,我们要先学到的是暴力递归而不是直接写出自底向上的递推
// 屎一样的题干,我就说怎么示例1不对劲,要爬出去,而不是length-1的位置,是length的位置
// return Math.min(planA(cost, cost.length-1), planA(cost, cost.length-2)); // 如果想爬出楼顶,那么肯定是前第一个和前第二个中的其中一个
int[] memo = new int[cost.length+1]; // 比cost多一个就说明了最后一个是楼顶
Arrays.fill(memo,-1);
return planB(memo, cost, cost.length); // 最长的
}
// 注意分析题目:真不知道这个表达的是什么,还以为写错了
// 要爬出楼顶,也就是最终的位置是length,而不是length-1
// 暴力递归的方法就是求出每一个节点的最小值,最终爬出的楼顶的位置一定是length-1或者是length-2
// 去除最小值即可
public int planA(int[] cost, int i){
if(i<0) return Integer.MAX_VALUE; // 表示不可到达
if(i==0 || i==1) return cost[i]; // 选择从坐标从第0个开始还是从第1个开始
int spend1 = planA(cost, i-1); // 从0或者1开始前第一个的花费
int spend2 = planA(cost, i-2); // 从0或者1开始到前第二个的花费
int spend = Math.min(spend1 , spend2) + cost[i]; // 去除子问题的最小值, 加上本身的花费
return spend;
}
// 写出了暴力递归的方式,自顶向下的动态规划也就是多了判断和记录的两个流程
// 分析memo,每一个节点都有对应的最小花费,记录这个最小花费即可,此时的长度就是 n
public int planB(int[] memo, int[] cost, int i){
if(i<0) return Integer.MAX_VALUE; // 返回最大值表示不可到达,看题目的要求是什么,如果求最大,那么返回最小值即可
if(i==0 || i==1) return cost[i];
if(memo[i] != -1) return memo[i]; // memo中初始化的数据要和题目元素不相干,初始化为-1即可
int spend1 = planB(memo, cost, i-1); // 从0或者1开始到前第一个的花费
int spend2 = planB(memo, cost, i-2); // 从0或者1开始到前第二个的花费
// 因为memo[cost.length] 就是需要返回的结果,但是此时的cost会越界,处理一下即可
// memo[i] = Math.min(spend1, spend2) + cost[i];
int spend3 = i==cost.length ? 0 : cost[i]; // 防止索引越界,当然可以在主函数中直接调用 i-1 和 i-2,不归类
memo[i] = Math.min(spend1, spend2) + spend3;
return memo[i];
}
}