代码随想录算法训练营Day38 | 理论基础 | 509. 斐波那契数 | 70. 爬楼梯 | 746. 使用最小花费爬楼梯

理论基础

动态规划理论

如果某一问题有很多重叠子问题,使用动态规划是最有效的。

动态规划的题型(常规)

  • 基础问题(例如斐波那契数、爬楼梯)
  • 背包问题
  • 打家劫舍
  • 股票问题
  • 子序列问题

动态规划五部曲

  1. 确定 dp 数组以及下标的含义
    • 真正的难点,想到一个合适的 dp 数组定义代表着对题目的正确理解,接下来的步骤就水到渠成了
  2. 确定递推公式
    • dp 核心,但理解 dp 数组的含义之后基本就能得到正确的 dp 公式
    • 要小心一些细节(例如 +1,-1,min,max)
  3. dp 数组的初始化
    • 非常重要!初始化同样体现对于题目的理解。不同的初始化,dp 递推公式也会不同。确定了自己的思路后,再确定初始化。
    • 初始化非常多样,全是 0,全是 1,开头是 0,开头是1其余全是0,都有可能
    • 最后一步举例推导,常常能捕捉到错误的初始化
  4. 确定遍历顺序
    • 对于二维数组来说,遍历顺序尤其重要
    • for loop 的嵌套顺序翻转,for loop 内的递推公式顺序,可能导致错误,也可能不产生影响。只有理解了每一种遍历顺序的原因,才能判断应该用哪一种顺序。
  5. 举例推导 dp 数组
    • dp 数组的最好的 debug 工具就是将 dp 数组直接打印出来,进行手推
    • 如果和自己的思路推演不一样,就确定是自己的代码实现出了问题
    • 如果和自己的思路推演一样,说明 dp 的前四步出错了

509. 斐波那契数

题目链接 | 理论基础

  1. dp 数组下标的含义:dp[i] 是第 i+1 个斐波那契数
  2. dp 递推公式:dp[i] = dp[i-1] + dp[i-2]
  3. dp 数组的初始化:dp[0] = 0, dp[1] = 1
  4. 遍历顺序:根据递推,简单的从小到大
  5. 举例推导 dp 数组:省略

本题是非常基础的 dp,主要是熟悉 dp 五部曲的方法论。作为基础的 dp,维护整个 dp 数组有些昂贵了,只需要维护前两个值即可。

class Solution:
    def fib(self, n: int) -> int:
        if n == 0:
            return 0
        if n == 1:
            return 1
        
        # initialization
        dp = [0] * (n+1)
        dp[1] = 1
        
        # dp formula
        for i in range(2, n+1):
            dp[i] = dp[i-1] + dp[i-2]
        
        return dp[-1]

70. 爬楼梯

题目链接 | 理论基础

  1. dp 数组下标的含义:dp[i] 是到达第 i 层的方法总数
  2. dp 递推公式:dp[i] = dp[i-1] + dp[i-2],因为到达第 i 层的方法有两种
    • 到达第 i - 2 层,然后一步走两阶,到达第 i 层
    • 到达第 i - 1 层,然后一步走一阶,到达第 i 层
  3. dp 数组的初始化:dp[0] = 1,其余均为 0
    • 由于起始位置就是“不存在的”第 0 层,所以初始化为 1,其余位置初始化为 0
    • 但更好的解释是,本题的输入是 n ≥ 1 n\geq1 n1,所以根本没必要考虑 dp[0] 的初始化,而是应该直接选择初始化 dp[1] = 1, dp[2] = 2,然后根据递推公式计算。
  4. 遍历顺序:根据递推,简单的从小到大
  5. 举例推导 dp 数组:
    idx = [0, 1, 2, 3, 4, 5]
    dps = [ , 1, 2, 3, 5, 8]
    
class Solution:
    def climbStairs(self, n: int) -> int:
        # dp[i] represents the number of ways to reach position i
        dp = [0] * (n+1)
        dp[0] = 1

        # dp formula
        for i in range(1, n+1):
            if i - 1 >= 0:
                dp[i] += dp[i-1]
            if i - 2 >= 0:
                dp[i] += dp[i-2]
        
        return dp[-1]

拓展

本题中,如果定义 dp[0] = 1,就可以发难了:为什么 dp[0] 一定要初始化为1,此时可能候选人就要强行给 dp[0] 应该是1找各种理由。那这就是一个考察点了,对 dp[i] 的定义理解的不深入。

另外,本题一个变种就是如果每一步能够爬 m 阶(本题 m=2),那么有多少种解法。区别只是在每一层内部加一个 for loop 循环。

746. 使用最小花费爬楼梯

题目链接 | 理论基础

本题的题目描述有些令人费解。个人来说,cost[i] 可以理解为离开第 i 层(向上爬 1 或 2 阶)所需要的代价。

  1. dp 数组下标的含义:dp[i] 是到达第 i 层的最少代价
  2. dp 递推公式:dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]),因为到达第 i 层的方法有两种
    • 花费 dp[i-2] 到达第 i - 2 层,然后花费 cost[i-2] 一步走两阶,到达第 i 层
    • 花费 dp[i-1] 到达第 i - 1 层,然后花费 cost[i-1] 一步走一阶,到达第 i 层
    • 选取这两种方法中花费更少的即可得到 dp[i]
  3. dp 数组的初始化:dp[0] = 1, dp[1] = 0,其余均为 0(其余位置会递推得到)
    • 题目允许直接从第 0 层或第 1 层开始,所以到达这两层的代价均为 0。
  4. 遍历顺序:根据递推,简单的从小到大
  5. 举例推导 dp 数组:
    cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
    dps = [0, 0, 1, 2, 2, 3, 3, 4, 4, 5, 6]
    
class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        # dp[i] represents the min cost to reach index i
        dp = [0] * (len(cost) + 1)
        dp[0] = 0
        dp[1] = 0

        # dp formula
        for i in range(2, len(cost)+1):
            dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])

        return dp[-1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值