“零钱兑换”是一道经典的算法题目,其主要问题是:给定不同面额的硬币和一个总金额,求出凑成总金额所需的最少硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。
解题思路
- 动态规划:使用动态规划是解决零钱兑换问题的常用方法。定义一个数组
dp
,其中dp[i]
表示凑成金额i
所需的最少硬币个数。 - 状态转移方程:对于每个金额
i
,遍历所有硬币面额coin
,如果i >= coin
,则dp[i] = min(dp[i], dp[i - coin] + 1)
。 - 初始化:
dp[0] = 0
,因为凑成金额0不需要任何硬币。对于其他金额,可以初始化为一个较大的数(如amount + 1
),表示初始时无法凑成该金额。 - 结果判断:最终
dp[amount]
即为所求,如果dp[amount]
仍为初始值,则表示无法凑成总金额,返回-1。
示例代码(C++)
#include <vector>
#include <algorithm>
using namespace std;int coinChange(vector<int>& coins, int amount) {vector<int> dp(amount + 1, amount + 1); // 初始化为一个较大的数dp[0] = 0; // 凑成金额0不需要任何硬币for (int i = 1; i <= amount; ++i) {for (int coin : coins) {if (i >= coin) {dp[i] = min(dp[i], dp[i - coin] + 1);}}}return dp[amount] > amount ? -1 : dp[amount];
}
说明
- 输入:
coins
是一个整数数组,表示不同面额的硬币;amount
是一个整数,表示总金额。 - 输出:返回凑成总金额所需的最少硬币个数,如果无法凑成,则返回-1。
这道题考察了动态规划的思想和实现能力,是面试中常见的算法题。
状态转移方程不太懂
好的,我们来详细解释一下“零钱兑换”问题中的状态转移方程。
问题背景
假设我们有若干种硬币,每种硬币有不同的面额。现在需要凑出一个总金额 amount
,目标是用最少的硬币数量凑出这个金额。
动态规划中的状态定义
在动态规划中,我们需要定义一个状态来表示问题的子问题。对于零钱兑换问题,我们定义:
dp[i]
表示凑成金额i
所需的最少硬币数量。
状态转移方程的含义
状态转移方程是动态规划的核心,它描述了如何从已知的子问题状态推导出当前问题的状态。
对于零钱兑换问题,状态转移方程是:
dp[i] = min(dp[i], dp[i - coin] + 1)
这个方程的意思是:
- 如果我们想凑成金额
i
,可以尝试用一个面额为coin
的硬币。 - 如果用了这个硬币,那么剩下的金额就是
i - coin
。 - 我们已经知道凑成金额
i - coin
所需的最少硬币数量是dp[i - coin]
。 - 因此,凑成金额
i
的硬币数量可以是dp[i - coin] + 1
(+1
是因为我们多用了一个硬币)。 - 但是,凑成金额
i
的方法可能有多种(比如可以用不同面额的硬币),所以我们要取所有可能方法中的最小值。
具体例子
假设硬币面额为 [1, 2, 5]
,目标金额是 11
。
初始化
我们先初始化一个数组 dp
,长度为 amount + 1
,并将所有值设为一个较大的数(比如 amount + 1
),表示初始时认为凑不出这些金额。同时,dp[0] = 0
,因为凑成金额 0
不需要任何硬币。
计算过程
我们逐步计算每个金额的最少硬币数:
-
计算
dp[1]
:- 尝试用面额为
1
的硬币:dp[1 - 1] + 1 = dp[0] + 1 = 0 + 1 = 1
。 - 因此,
dp[1] = 1
。
- 尝试用面额为
-
计算
dp[2]
:- 尝试用面额为
1
的硬币:dp[2 - 1] + 1 = dp[1] + 1 = 1 + 1 = 2
。 - 尝试用面额为
2
的硬币:dp[2 - 2] + 1 = dp[0] + 1 = 0 + 1 = 1
。 - 因此,
dp[2] = min(2, 1) = 1
。
- 尝试用面额为
-
计算
dp[3]
:- 尝试用面额为
1
的硬币:dp[3 - 1] + 1 = dp[2] + 1 = 1 + 1 = 2
。 - 尝试用面额为
2
的硬币:dp[3 - 2] + 1 = dp[1] + 1 = 1 + 1 = 2
。 - 因此,
dp[3] = min(2, 2) = 2
。
- 尝试用面额为
-
计算
dp[4]
:- 尝试用面额为
1
的硬币:dp[4 - 1] + 1 = dp[3] + 1 = 2 + 1 = 3
。 - 尝试用面额为
2
的硬币:dp[4 - 2] + 1 = dp[2] + 1 = 1 + 1 = 2
。 - 因此,
dp[4] = min(3, 2) = 2
。
- 尝试用面额为
-
计算
dp[5]
:- 尝试用面额为
1
的硬币:dp[5 - 1] + 1 = dp[4] + 1 = 2 + 1 = 3
。 - 尝试用面额为
2
的硬币:dp[5 - 2] + 1 = dp[3] + 1 = 2 + 1 = 3
。 - 尝试用面额为
5
的硬币:dp[5 - 5] + 1 = dp[0] + 1 = 0 + 1 = 1
。 - 因此,
dp[5] = min(3, 3, 1) = 1
。
- 尝试用面额为
……以此类推,直到计算出 dp[11]
。
总结
状态转移方程 dp[i] = min(dp[i], dp[i - coin] + 1)
的含义是:
- 对于每个金额
i
,尝试用每一种硬币coin
。 - 如果用了这个硬币,那么剩下的金额是
i - coin
,而凑成i - coin
的最少硬币数是已知的(dp[i - coin]
)。 - 因此,凑成金额
i
的硬币数可以是dp[i - coin] + 1
。 - 最后取所有可能方法中的最小值,作为凑成金额
i
的最少硬币数。
希望这个解释能帮助你更好地理解状态转移方程!
我还是不理解
好的,我来用更通俗的方式解释“零钱兑换”问题。
问题背景
假设你是一名收银员,顾客需要找零,你手上有不同面额的硬币。你的任务是用最少的硬币数量凑出顾客需要的零钱金额。
举个例子
假设你手上有以下几种硬币:
- 1元硬币
- 3元硬币
- 4元硬币
现在顾客需要找零 10元,你会怎么凑呢?
思考过程
- 目标:用最少的硬币凑出10元。
- 方法:你可以尝试不同的组合:
- 用10个1元硬币(10个硬币)
- 用3个3元硬币 + 1个1元硬币(4个硬币)
- 用2个4元硬币 + 2个1元硬币(4个硬币)
- 用1个4元硬币 + 2个3元硬币(3个硬币)
经过比较,你会发现 1个4元硬币 + 2个3元硬币 是最优解,只需要 3个硬币。
零钱兑换问题的核心
-
输入:
- 一个硬币数组,比如
[1, 3, 4]
,表示你手上的硬币面额。 - 一个目标金额,比如
10
,表示需要凑出的金额。
- 一个硬币数组,比如
-
输出:
- 返回最少需要的硬币数量。如果无法凑出目标金额,返回
-1
。
- 返回最少需要的硬币数量。如果无法凑出目标金额,返回
-
难点:
- 你不能简单地从最大的硬币开始凑,因为可能无法凑出目标金额。比如,目标金额是 6元,硬币面额是
[4, 3, 1]
,如果你先用一个4元硬币,剩下的2元就无法凑出(因为没有2元硬币)。
- 你不能简单地从最大的硬币开始凑,因为可能无法凑出目标金额。比如,目标金额是 6元,硬币面额是
动态规划的思路
动态规划是一种分步解决问题的方法,它把大问题拆成小问题,逐步求解。
-
定义状态:
- 假设
dp[i]
表示凑出金额i
所需的最少硬币数量。
- 假设
-
状态转移:
- 假设你已经知道凑出金额
i - coin
所需的最少硬币数量(dp[i - coin]
),那么加上一个面额为coin
的硬币,就可以凑出金额i
。 - 所以,
dp[i] = min(dp[i], dp[i - coin] + 1)
,意思是:凑出金额i
的最少硬币数,要么是之前已经计算好的值,要么是凑出金额i - coin
的硬币数加1。
- 假设你已经知道凑出金额
-
初始化:
dp[0] = 0
,因为凑出金额0不需要任何硬币。- 其他金额可以初始化为一个很大的数(比如
amount + 1
),表示目前无法凑出。
-
最终结果:
- 如果
dp[amount]
仍然是初始值(比如amount + 1
),说明无法凑出目标金额,返回-1
。 - 否则,返回
dp[amount]
。
- 如果
用例子解释动态规划
假设硬币面额是 [1, 3, 4]
,目标金额是 6
,我们逐步计算 dp
数组:
金额(i) | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
dp[i] | 0 | 1 | 2 | 1 | 1 | 2 | 2 |
- 金额0:
dp[0] = 0
,凑出金额0不需要硬币。 - 金额1:只能用1个1元硬币,所以
dp[1] = 1
。 - 金额2:只能用2个1元硬币,所以
dp[2] = 2
。 - 金额3:可以用3个1元硬币(
dp[3] = dp[0] + 1 = 1
),也可以用1个3元硬币(dp[3] = 1
)。 - 金额4:可以用4个1元硬币(
dp[4] = dp[0] + 1 = 1
),也可以用1个4元硬币(dp[4] = 1
)。 - 金额5:可以用5个1元硬币(
dp[5] = dp[0] + 1 = 2
),也可以用1个3元硬币 + 2个1元硬币(dp[5] = dp[2] + 1 = 2
)。 - 金额6:可以用6个1元硬币(
dp[6] = dp[0] + 1 = 2
),也可以用2个3元硬币(dp[6] = dp[0] + 2 = 2
)。
最终,凑出金额6最少需要2个硬币。
总结
零钱兑换问题的核心是用最少的硬币凑出目标金额。动态规划通过逐步计算每个金额所需的最少硬币数,最终得到答案。希望这个解释能帮助你理解!