Leetcode刷题笔记45:动态规划7(70. 爬楼梯(进阶)-322. 零钱兑换-279.完全平方数)

115 阅读4分钟

导语

leetcode刷题笔记记录,本篇博客是动态规划部分,主要记录题目包括:

Leetcode 70. 爬楼梯

题目描述

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

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

 

示例 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

进阶:改为:一步一个台阶,两个台阶,三个台阶,.......,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?

解法

这个进阶问题可以转化为完全背包问题,1阶,2阶,.... m阶就是物品,楼顶就是背包。每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。问跳到楼顶有几种方法其实就是问装满背包有几种方法。动规五部曲如下:

  1. dp[i]表示爬到有i个台阶的楼顶,有dp[i]种方法
  2. 递推公式:这里相当于每个数字就是重量和价值,那么递推公式为:dp[j]+=dp[ji]dp[j]+=dp[j-i]
  3. 初始化:既然递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0。下标非0的dp[i]初始化为0,因为dp[i]是靠dp[i-j]累计上来的,dp[i]本身为0这样才不会影响结果。
  4. 这是排列问题,先1后2和先2后1不一样,所以应该先遍历背包,再遍历物品。
  5. 打印dp数组

代码如下:

class Solution:
    def climbStairs(self, n: int) -> int:
        # 初始化一个长度为 n+1 的动态规划数组,用于存储到达每一阶台阶的方法数量
        dp = [0] * (n + 1)
        
        # 边界条件:到达第 0 阶台阶(起点)有 1 种方法
        dp[0] = 1
        
        # 从第 1 阶台阶开始遍历到第 n 阶台阶
        for j in range(1, n + 1):
            # 我们可以用 1 阶或 2 阶来到达当前 j 阶台阶,
            # 所以需要遍历这两种可能性
            for i in range(1, 2 + 1):  # 这里直接用 range(1, 3) 也是可以的
                # 检查 j-i 是否非负,这样我们就能确定能否用 i 阶来到达 j 阶
                if j - i >= 0:
                    # dp[j] 是到达 j 阶的方法数量,dp[j-i] 是到达 j-i 阶的方法数量。
                    # 从 j-i 阶走 i 阶可以到达 j 阶,所以要把 dp[j-i] 加到 dp[j] 上
                    dp[j] += dp[j - i]
                    
        # 返回到达第 n 阶台阶的方法数量
        return dp[n]

Leetcode 322. 零钱兑换

题目描述

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

 

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

示例 2:

输入: coins = [2], amount = 3
输出: -1

示例 3:

输入: coins = [1], amount = 0
输出: 0

 

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104

解法

这道题目是求解最少的凑成方式,所以只需要将max操作修改为min操作,同时,在初始化时,数组的非零下标元素应该取最大值。打印dp数组示例(参考:代码随想录)如下:

image.png

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        # 初始化一个动态规划数组 dp,长度为 amount+1。
        # dp[i] 表示凑成总金额 i 所需的最少硬币数量。
        # 初始化为 float('inf'),意味着开始时,任何金额都是无法凑成的。
        dp = [0] + [float('inf')] * amount
        
        # 遍历每一个硬币
        for i in range(len(coins)):
            # 从该硬币面值开始,遍历到总金额
            for j in range(coins[i], amount + 1):
                # 更新到达金额 j 所需的最少硬币数量。
                # 两种可能:1) 不使用当前硬币 coins[i],即 dp[j] 不变;
                #           2) 使用一枚当前硬币,然后加上凑成 j-coins[i] 所需的硬币数,即 dp[j-coins[i]]+1。
                # 我们取两者的最小值。
                dp[j] = min(dp[j - coins[i]] + 1, dp[j])
        
        # 如果最终凑成总金额 amount 所需的硬币数仍为正无穷大,则表示无解。
        if dp[amount] == float('inf'):
            return -1
        
        # 返回凑成总金额所需的最少硬币数。
        return dp[amount]

Leetcode 279. 完全平方数

题目描述

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。

 

示例 1:

输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4

示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9

 

提示:

  • 1 <= n <= 104

解法

这道题目看上去和之前套路又不一样,实际上,我们可以把1,4,9,16……这些完全平方数当做coins,最终的target当做要凑的零钱,那么这道题目就转变成了上一道题目,而且还保证一定有解(因为1也是完全平方数),所以只需要稍微修改代码,就可以得到解答,完整代码如下:

from math import sqrt
from typing import List

class Solution:
    def numSquares(self, n: int) -> int:
        # 初始化一个动态规划数组 dp,长度为 n+1。
        # dp[i] 表示和为 i 的完全平方数的最少数量。
        # 初始化为 float('inf'),意味着开始时,任何和都是无法用完全平方数凑成的。
        dp = [0] + [float('inf')] * n
        
        # 计算 n 的平方根的整数部分,这个值用于生成所有可能的完全平方数
        upper = int(sqrt(n))
        
        # 生成一个完全平方数列表,这里的完全平方数就像是"硬币"
        coins = [i*i for i in range(1, upper+1)]
        
        # 遍历每一个完全平方数(硬币)
        for i in range(len(coins)):
            # 从该完全平方数(硬币)面值开始,遍历到目标和
            for j in range(coins[i], n+1):
                # 更新凑成和 j 所需的最少完全平方数数量。
                # 取两者的最小值:1) 不使用当前完全平方数,即 dp[j] 不变;
                #                 2) 使用一个当前完全平方数,然后加上凑成 j-coins[i] 所需的完全平方数数量,即 dp[j-coins[i]] + 1。
                dp[j] = min(dp[j], dp[j - coins[i]] + 1)
        
        # 返回凑成目标和所需的最少完全平方数数量
        return dp[n]

总结

image.png