零钱兑换

一、题目描述

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

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

示例 2:

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

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

二、解题思路 & 代码

状态转移方程:

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [float('inf')] * (amount + 1)
        dp[0] = 0
        
        for coin in coins:
            for x in range(coin, amount + 1):
                dp[x] = min(dp[x], dp[x - coin] + 1)
        return dp[amount] if dp[amount] != float('inf') else -1 


复杂度分析

  1. 时间复杂度:O(Sn),其中 S 是金额,n 是面额数。我们一共需要计算 O(S) 个状态,S 为题目所给的总金额。对于每个状态,每次需要枚举 n 个面额来转移状态,所以一共需要 O(Sn) 的时间复杂度。
  2. 空间复杂度:O(S)。DP 数组需要开长度为总金额 S 的空间。

======================================================================================================================================================================================================================================================

零钱兑换 II

一、题目描述

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1:

输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。

示例 3:

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

注意:

你可以假设:

0 <= amount (总金额) <= 5000
1 <= coin (硬币面额) <= 5000
硬币种类不超过 500 种
结果符合 32 位符号整数

二、解题思路 & 代码

子问题:
p r o b l e m ( k , i ) = p r o b l e m ( k − 1 , i ) + p r o b l e m ( k , i − k ) problem(k,i) = problem(k-1, i) + problem(k, i-k) problem(k,i)=problem(k1,i)+problem(k,ik)
含义为凑成总金额i的硬币组合数等于凑成总金额硬币 i − 1 , i − 2 , i − 5 , . . . i-1, i-2, i-5,... i1,i2,i5,... 的子问题之和。

我们发现这个子问题定义居然和我们之前泛化的爬楼梯问题居然是一样的,那后面的状态数组和状态转移方程也是一样的,所以当前问题的代码可以在之前的泛化爬楼梯问题中进行修改而得。

即前 k 个硬币凑齐金额i的组合数等于前k-1个硬币凑齐金额i的组合数加上在原来 i − k i-k ik 的基础上使用硬币的组合数。说的更加直白一点,那就是用前k的硬币凑齐金额i,要分为两种情况开率,一种是没有用前k-1个硬币就凑齐了,一种是前面已经凑到了i-k,现在就差第k个硬币了。

状态数组就是 D P [ k ] [ i ] DP[k][i] DP[k][i], 即前k个硬币凑齐金额i的组合数。

这里不再是一维数组,而是二维数组。第一个维度用于记录当前组合有没有用到硬币k,第二个维度记录现在凑的金额是多少?如果没有第一个维度信息,当我们凑到金额i的时候,我们不知道之前有没有用到硬币k。

因为这是个组合问题,我们不关心硬币使用的顺序,而是硬币有没有被用到。是否使用第k个硬币受到之前情况的影响。

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        dp = [0] * (amount + 1)
        dp[0] = 1
        
        for coin in coins:
            for x in range(coin, amount + 1):
                dp[x] += dp[x - coin]
        return dp[amount]

复杂度分析

  1. 时间复杂度:O(N×amount)。其中 N 为 coins 数组的长度。
  2. 空间复杂度:O(amount),dp 数组使用的空间。

参考:

  1. Leetcode题解
  2. leetCode题解 II