代码随想录刷题笔记 DAY 37 | 动态规划理论基础 | 斐波那契数 No.509 | 爬楼梯 No.70 | 使用最小花费爬楼梯 No.746

文章目录

    • [Day 37](#Day 37)
      • [00. 动态规划理论基础](#00. 动态规划理论基础)
      • [01. 斐波那契数(No. 509)](#01. 斐波那契数(No. 509))
        • [<1> 题目](#<1> 题目)
        • [<2> 笔记](#<2> 笔记)
        • [<3> 代码](#<3> 代码)
      • [02. 爬楼梯(No. 70)](#02. 爬楼梯(No. 70))
        • [<1> 题目](#<1> 题目)
        • [<2> 笔记](#<2> 笔记)
        • [<3> 代码](#<3> 代码)
      • [03. 使用最小花费爬楼梯(No. 746)](#03. 使用最小花费爬楼梯(No. 746))
        • [<1> 题目](#<1> 题目)
        • [<2> 笔记](#<2> 笔记)
        • [<3> 代码](#<3> 代码)

Day 37

00. 动态规划理论基础

最常见的动态规划题目其实就是 求最值,比如说股票问题、背包问题,都是在求使用怎样的策略能使得整个系统达到一个最优化的状态。

这是否和贪心比较类似呢?

其实贪心算法和动态规划算法的区别还是比较大的,贪心算法每一次的最优解一定 包含 上一次的最优解,是局部的最优推出全局的最优,而动态规划的最优解不一定包含前一次的最优解,而是有可能是由更前面的部分推出的,所以通常通过 dp[] 数组来将前面的所有最优解来保存下来。

动态规划其实是一个 穷举 的过程,得到最优解的前提就是要将所有的可能导致最优解的情况列出来,逐步推出最终的结果,而贪心更像是确定了一个路线,直接来走这个最优的路线,但这种最优通常是一种经验性的,较难推导的方式,相信做过贪心部分的朋友应该深有体会,这也就导致贪心得到的可能不是最优解,但相对的时间复杂度较低,而动态规划本质是穷举就会导致其时间复杂度相对较高。

再来谈谈动态规划和暴力解法的区别,比如说斐波那契数列,使用递归来求会导致大量的重复计算,所以考虑引入备忘录,也就是记忆搜索的方法,记忆搜索的方法是自顶向下的,也就是要算 f(5) 要递归到 f(1) 才开始计算结果,而且由于递归的限制,思考其实可以采用自底向下的方式,从 f(1) 开始向上层递归,这其实就是动态规划的方法。

01. 斐波那契数(No. 509)

题目链接

代码随想录题解

<1> 题目

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 01 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1

F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n)

示例 1:

输入:n = 2

输出:1

解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:

输入:n = 3

输出:2

解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:

输入:n = 4

输出:3

解释:F(4) = F(3) + F(2) = 2 + 1 = 3

提示:

  • 0 <= n <= 30
<2> 笔记

这道题目相信大家都能比较容易的写出来,这里以本题为案例看看动态规划的一些小特点。

动态规划比较重要的一个点就是 状态 ,解题的关键在于能否列出正确的 状态转移方程 ,以及这个状态转移方程最终能否穷举出问题的最终答案,其实这就要求这个算法问题具备 最优子结构 ;因为需要不断用到前面的状态来推导后面状态的最优,这就会引出大量的重复的计算这也就是常说的 重叠子问题,所以使用暴力解法的效率很低。

这道斐波那契数列其实就很好的展示了重叠子问题,但其实并不存在重叠子问题,所以严格上并不算动态规划的题目。

比如计算 f(5),画出递归树

可以看出其实是计算了两遍 f(3) 的,如果说计算的数字更大,子问题的个数会以指数级的速度增长,时间复杂度为 2^n^。这就是所谓的重叠子问题。

可以注意到 f(n) 的状态其实是由 f(n - 1)f(n - 2) 推导出来的,转移的方式其实就是两者加和,这样其实就很容易看出 dp 数组的含义,就是 dp[n] = f(n),状态转移公式也顺带推了出来(这就是本题较为简单的原因),因为这几步几乎不用怎么思考。

直接写出代码。

<3> 代码
java 复制代码
class Solution {
    public int fib(int n) {
        if (n == 0) return 0;
        if (n <= 2) return 1;
        int[] dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

02. 爬楼梯(No. 70)

题目链接

代码随想录题解

<1> 题目

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2

输出:2

解释:有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

示例 2:

输入:n = 3

输出:3

解释:有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶

提示:

  • 1 <= n <= 45
<2> 笔记

本题其实也没有引入太多的递归思想,其实可以看作是下一题 使用最小花费爬楼梯 的一个导入。

本题的 dp 定义其实是较为简单的,因为要得出 n 阶台阶由几种走法,可以想到就是用 dp[n] 来代表共 n 阶由多少种走法。

爬到第一层楼有一种方法,爬到第二层有两种方法,那爬到第三层其实就有三种方法,一种是通过第一层跨两步,另一种是第二层跨一步。

很多朋友做这道题的时候会有一个疑问的地方,就是我从第一层走一步再走一步算不算一种方法呢?或者说从第四层走两次到第五层,这里想不通的朋友是因为没有将目光放到全局,其实将这些方式写出来得到的数列是相同的,比如 n = 3 的时候 1 1 1,可以理解成从 2 到 3 的一部分,也可以理解成从 1 跨两步到 3,计算的话其实就计算其中的一部分即可,否则会出现冲突。

使用递归五步法来规范一下:

  1. 确认 dp 的含义:爬到第i层楼梯,有dp[i]种方法
  2. 确认递推公式:dp[i] = dp[i - 1] + dp[i - 2]
  3. dp 数组如何初始化:题目中说了n是一个正整数,所以 dp[0] 是没有意义的,而递归推出后面的部分有需要两个元素,所以初始化就初始化 dp[1]dp[2]
  4. 递归的遍历顺序:需要用到前面的元素,从前向后遍历
  5. 尝试举例推导
<3> 代码
java 复制代码
class Solution {
    public int climbStairs(int n) {
        if (n < 3) return n;
        int[] dp = new int[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];
    }
}

03. 使用最小花费爬楼梯(No. 746)

题目链接

代码随想录题解

<1> 题目

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

示例 1:

输入:cost = [10,15,20]

输出:15

解释:你将从下标为 1 的台阶开始。

  • 支付 15 ,向上爬两个台阶,到达楼梯顶部。
    总花费为 15 。

示例 2:

输入:cost = [1,100,1,1,1,100,1,1,100,1]

输出:6

解释:你将从下标为 0 的台阶开始。

  • 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
  • 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
  • 支付 1 ,向上爬一个台阶,到达楼梯顶部。
    总花费为 6 。

提示:

  • 2 <= cost.length <= 1000
  • 0 <= cost[i] <= 999
<2> 笔记

有了上一道题的铺垫其实本题就比较容易了,到达台阶 n 有两种方式分别是从 n - 1n - 2 到达,所以需要有一个数组来存储之前的状态。

  1. 确定dp数组以及下标的含义,使用动态规划,就要有一个数组来记录状态,本题只需要一个一维数组dp[i]就可以了,dp[i]的定义:到达第i台阶所花费的最少体力为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] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
  3. dp 数组如何初始化:和上面题目相同,只需要初始化两个即可,也就是 dp[0]dp[1],分别初始化成 cost[0]ocst[1]
  4. 确定遍历顺序:和上题相同,都是自底向上,从前向后遍历
  5. 举例推导,发现没有问题

通过上面的梳理就能写出代码

<3> 代码
java 复制代码
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        if (n < 2) {
            return 0;
        }
        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 0;
        for (int i = 2; i <= n; i++) {
            dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]); 
        }
        return dp[n];
        
    }
}
相关推荐
小码农<^_^>9 分钟前
优选算法精品课--滑动窗口算法(一)
算法
让学习成为一种生活方式11 分钟前
R包下载太慢安装中止的解决策略-R语言003
java·数据库·r语言
Mephisto.java11 分钟前
【大数据学习 | kafka高级部分】kafka中的选举机制
大数据·学习·kafka
羊小猪~~11 分钟前
神经网络基础--什么是正向传播??什么是方向传播??
人工智能·pytorch·python·深度学习·神经网络·算法·机器学习
晨曦_子画17 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
Yawesh_best26 分钟前
思源笔记轻松连接本地Ollama大语言模型,开启AI写作新体验!
笔记·语言模型·ai写作
软工菜鸡37 分钟前
预训练语言模型BERT——PaddleNLP中的预训练模型
大数据·人工智能·深度学习·算法·语言模型·自然语言处理·bert
南宫生40 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
Heavydrink1 小时前
HTTP动词与状态码
java
ktkiko111 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc