代码随想录算法训练营第38天(动态规划01 ● 理论基础 ● 509. 斐波那契数 ● 70. 爬楼梯 ● 746. 使用最小花费爬楼梯

动态规划part01

  • 理论基础
  • 509. 斐波那契数
  • 70. 爬楼梯
    • 解题思路
  • 746. 使用最小花费爬楼梯
    • 解题思路

今天正式开始动态规划!

理论基础

理论基础讲解
视频讲解

动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的

动态规划五步曲

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

动态规划出bug的思考步骤:
写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍
再写代码,如果代码没通过就打印dp数组,看看是不是和自己预先推导的哪里不一样
如果打印出来和自己预先模拟推导是一样的那么就是自己的递归公式、初始化或者遍历顺序有问题了

509. 斐波那契数

很简单的动规入门题,但简单题使用来掌握方法论的,还是要有动规五部曲来分析。
题目链接: 509. 斐波那契数
视频/文章讲解:[ 509. 斐波那契数 ](https://programmercarl.com/0509.%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.html

// 动态规划
class Solution {
    public int fib(int n) {
        int f1 = 0;
        int f2 = 1;
        int f3 = 0;
        if(n <= 1) return n;
        for(int i = 2; i <= n; i++){
            f3 = f1 + f2;
            f1 = f2;
            f2 = f3;
            System.out.println(f3);
        }
        return f3;
    }
}

70. 爬楼梯

本题大家先自己想一想, 之后会发现,和 斐波那契数 有点关系。
题目链接: 70. 爬楼梯
视频/文章讲解: 70. 爬楼梯

解题思路

卡哥的思路是:不考虑dp[0]如何初始化,只初始化dp[1] = 1,dp[2] = 2,然后从i = 3开始递推,这样才符合dp[i]的定义。
一个绝佳的大厂面试题,第一道题就是单纯的爬楼梯,然后看候选人的代码实现,如果把dp[0]的定义成1了,就可以发难了,为什么dp[0]一定要初始化为1,此时可能候选人就要强行给dp[0]应该是1找各种理由。那这就是一个考察点了,对dp[i]的定义理解的不深入。

然后可以继续发难,如果一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。这道题目leetcode上并没有原题,绝对是考察候选人算法能力的绝佳好题。

// 动态规划
class Solution {
    public int climbStairs(int n) {
        int f0 = 1;
        int f1 = 1;
        int sum = 0;
        if(n == 1) return n;
        for(int i = 2; i <= n; i++){
            sum = f0 + f1;
            f0 = f1;
            f1 = sum;
        }
        return sum;       
    }
}
// 卡哥想法 不考虑dp[0]
class Solution {
    public int climbStairs(int n) {
        int[] dp = new int[n + 2];  // 不理解这里为什么 n+1 无法通过
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];       
    }
}

746. 使用最小花费爬楼梯

这道题目力扣改了题目描述了,现在的题目描述清晰很多,相当于明确说 第一步是不用花费的。
更改题目描述之后,相当于是 文章中 「拓展」的解法
题目链接: 746. 使用最小花费爬楼梯
文章/视频链接: 746. 使用最小花费爬楼梯

解题思路

这道题需要写一下动态规划五部曲帮助理解了

  1. 确定dp数组以及下标的含义
    dp[i]的定义:到达第i台阶所花费的最少体力为dp[i]。
  2. 确定递推公式
    可以有两个途径得到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]。
    那么究竟是选从dp[i - 1]跳还是从dp[i - 2]跳呢?
    一定是选最小的,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
  3. dp数组初始化
    新题目描述中明确说了 “你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。” 也就是说 到达 第 0 个台阶是不花费的,但从 第0 个台阶 往上跳的话,需要花费 cost[0]。
    所以初始化dp[0] = 0,dp[1] = 0;
  4. 确定遍历顺序
    因为是模拟台阶,而且dp[i]由dp[i-1]dp[i-2]推出,所以是从前到后遍历cost数组就可以了。
  5. 举例推导dp数组
    代码随想录算法训练营第38天(动态规划01 ● 理论基础 ● 509. 斐波那契数 ● 70. 爬楼梯 ● 746. 使用最小花费爬楼梯_第1张图片
// 动态规划
// 第一步不支付费用,从该步跳才需要支付该步的费用
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];
    }
}

你可能感兴趣的:(算法,动态规划,数据结构,java,leetcode)