代码随想录算法训练营第四十五天 |70. 爬楼梯、322. 零钱兑换、279. 完全平方数

95 阅读4分钟

70. 爬楼梯

代码随想录文章讲解

动态规划

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

  • dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法
  • 本题呢,dp[i]有几种来源,dp[i - 1],dp[i - 2],dp[i - 3] 等等,即:dp[i - j]那么递推公式为:dp[i] += dp[i - j]
  • 既然递归公式是 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这样才不会影响结果
  • 这是背包里求排列问题,即:1、2 步 和 2、1 步都是上三个台阶,但是这两种方法不一样! 所以需将target放在外循环,将nums放在内循环。每一步可以走多次,这是完全背包,内循环需要从前向后遍历。
class Solution:
    def climbStairs(self, n: int) -> int:
        dp = [0]*(n + 1)
        dp[0] = 1
        m = 2
        # 遍历背包
        for j in range(n + 1):
            # 遍历物品
            for step in range(1, m + 1):
                if j >= step:
                    dp[j] += dp[j - step]
        return dp[n]

322. 零钱兑换

代码随想录文章讲解

动态规划

  • dp[j]:凑足总额为j所需钱币的最少个数为dp[j]
  • 得到dp[j](考虑coins[i]),只有一个来源,dp[j - coins[i]](没有考虑coins[i])。凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j](考虑coins[i])所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
  • 首先凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;
  • 考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。所以下标非0的元素都是应该是最大值。
  • 本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数。所以本题并不强调集合是组合还是排列。如果求组合数就是外层for循环遍历物品,内层for遍历背包如果求排列数就是外层for遍历背包,内层for循环遍历物品
  • 所以本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包或者外层for遍历背包,内层for循环遍历物品都是可以的! 那么我采用coins放在外循环,target在内循环的方式。本题钱币数量可以无限使用,那么是完全背包。所以遍历的内循环是正序。综上所述,遍历顺序为:coins(物品)放在外循环,target(背包)在内循环。且内循环正序。
class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [amount + 1] * (amount + 1)
        dp[0] = 0
        
        for coin in coins:
            for j in range(coin, amount + 1):
                dp[j] = min(dp[j], dp[j-coin] + 1)
        
        return dp[amount] if dp[amount] < amount + 1 else -1

279. 完全平方数

代码随想录文章讲解

动态规划

  • dp[j]:和为j的完全平方数的最少数量为dp[j]
  • dp[j] 可以由dp[j - i * i]推出, dp[j - i * i] + 1 便可以凑成dp[j]。此时我们要选择最小的dp[j],所以递推公式:dp[j] = min(dp[j - i * i] + 1, dp[j]);
  • dp[0]表示 和为0的完全平方数的最小数量,那么dp[0]一定是0。有同学问题,那0 * 0 也算是一种啊,为啥dp[0] 就是 0呢?看题目描述,找到若干个完全平方数(比如 1, 4, 9, 16, ...),题目描述中可没说要从0开始,dp[0]=0完全是为了递推公式。非0下标的dp[j]应该是多少呢?从递归公式dp[j] = min(dp[j - i * i] + 1, dp[j]);中可以看出每次dp[j]都要选最小的,所以非0下标的dp[j]一定要初始为最大值,这样dp[j]在递推的时候才不会被初始值覆盖
  • 本题是求最小数!所以本题外层for遍历背包,内层for遍历物品,还是外层for遍历物品,内层for遍历背包,都是可以的!
class Solution:
    def numSquares(self, n: int) -> int:
        max_num = math.floor(math.sqrt(n))
        
        nums = [ num ** 2 for num in range(1, max_num + 1)]
        dp = [n+1] * (n + 1)
        dp[0] = 0
        
        for num in nums:
            for j in range(num, n + 1):
                dp[j] = min(dp[j], dp[j-num] + 1)
        
        return dp[n]