首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 合格程序员的第一步:算法与数据结构
02 | 如何事半功倍地学习算法与数据结构
03 | 如何计算算法的复杂度
04 | 如何通过LeetCode来进行算法题目练习
05 | 理论讲解:数组&链表
06 | 面试题:反转一个单链表&判断链表是否有环
07 | 理论讲解:堆栈&队列
08 | 面试题:判断括号字符串是否有效
09 | 面试题:用队列实现栈&用栈实现队列
10 | 理论讲解:优先队列
11 | 面试题:返回数据流中的第K大元素
12 | 面试题:返回滑动窗口中的最大值
13 | 理论讲解:哈希表
14 | 面试题:有效的字母异位词
15 | 面试题:两数之和
16 | 面试题:三数之和
17 | 理论讲解:树&二叉树&二叉搜索树
18 | 面试题:验证二叉搜索树
19 | 面试题:二叉树&二叉搜索树的最近公共祖先
20 | 理论讲解:二叉树遍历
21 | 理论讲解:递归&分治
22 | 面试题:Pow(x,n)
23 | 面试题:求众数
24 | 理论讲解:贪心算法
25 | 面试题:买卖股票的最佳时机
26 | 理论讲解:广度优先搜索
27 | 理论讲解:深度优先搜索
28 | 面试题:二叉树层次遍历
29 | 面试题:二叉树的最大和最小深度
30 | 面试题:生成有效括号组合
31 | 理论讲解:剪枝
32 | 面试题:N皇后问题
33 | 面试题:数独问题
34 | 理论讲解:二分查找
35 | 面试题:实现一个求解平方根的函数
36 | 理论讲解:字典树
37 | 面试题:实现一个字典树
38 | 面试题:二维网格中的单词搜索问题
39 | 理论讲解:位运算
40 | 面试题:统计位1的个数
41 | 面试题:2的幂次方问题&比特位计数问题
42 | 面试题:N皇后问题的另一种解法
43 | 理论理解:动态规划(上)
44 | 理论理解:动态规划(下)
45 | 面试题:爬楼梯
46 | 面试题:三角形的最小路径和
47 | 面试题:乘积最大子序列
48 | 面试题:股票买卖系列
49 | 面试题:最长上升子序列
50 | 面试题:零钱兑换
51 | 面试题:编辑距离
52 | 理论讲解:并查集
53 | 面试题:岛屿的个数&朋友圈(上)
54 | 面试题:岛屿的个数&朋友圈(下)
55 | 理论讲解: LRU Cache
56 | 面试题:设计和实现一个LRU Cache缓存机制
57 | 理论讲解:布隆过滤器
当前位置:
首页>>
技术小册>>
算法面试通关 50 讲
小册名称:算法面试通关 50 讲
### 50 | 面试题:零钱兑换 在算法面试中,零钱兑换(Coin Change)是一个经典而富有挑战性的动态规划问题,它不仅考验了面试者对动态规划思想的理解深度,还要求在有限的时间内设计出高效、准确的解决方案。本讲将深入剖析零钱兑换问题的本质,提供多种解题思路,并重点讲解动态规划方法的应用,最后通过代码实现加深理解。 #### 一、问题描述 给定一个目标金额`n`(整数)和一个硬币种类列表`coins`(整数数组,每种硬币的面值),你需要计算并返回凑成目标金额所需的最少硬币个数。如果无法凑成目标金额,则返回-1。 #### 二、问题分析 1. **暴力解法**:最直接的方法是尝试所有可能的硬币组合,但这种方法的时间复杂度会随着目标金额和硬币种类的增加而呈指数级增长,对于稍大的输入将变得不可行。 2. **贪心算法**:虽然直觉上可能会想到按硬币面值从大到小(或从小到大)的顺序尝试使用硬币,但这种方法并不能保证得到最少硬币个数的解。例如,对于`coins = [1, 5, 10]`和`n = 15`,贪心算法可能会先选择10,再选5,最后选两个1,共8个硬币,而最优解是三个5。 3. **动态规划**:鉴于上述方法的局限性,动态规划成为解决零钱兑换问题的最佳选择。动态规划通过保存子问题的解来避免重复计算,从而显著降低时间复杂度。 #### 三、动态规划解法 ##### 1. 定义状态 设`dp[i]`表示凑成金额`i`所需的最少硬币个数。注意,这里的`i`从0开始,即`dp[0] = 0`,表示凑成金额为0不需要任何硬币。 ##### 2. 状态转移方程 对于任意金额`i`(`i > 0`),我们可以通过遍历硬币种类列表`coins`,尝试从每种硬币开始凑成金额`i`。如果当前硬币的面值为`coin`,且`i - coin`是一个有效的、已经计算过的金额,则我们可以将`dp[i]`更新为`dp[i - coin] + 1`(即使用当前硬币后,剩余金额所需的最少硬币数加1)和之前`dp[i]`的较小值。如果所有硬币都无法用于凑成金额`i`,则`dp[i]`应保持为一个较大的值(如初始化为`n+1`),表示无法凑成。 状态转移方程可表示为: \[ dp[i] = \min_{coin \in coins} (dp[i - coin] + 1) \quad \text{if } i - coin \geq 0 \text{ 且 } dp[i-coin] \neq \text{初始大值} \] 如果所有`dp[i-coin]`都是初始大值,则`dp[i]`也应保持为初始大值。 ##### 3. 初始化 - `dp[0] = 0`,表示凑成金额为0不需要任何硬币。 - 对于其他`dp[i]`(`i > 0`),初始化为一个较大的值(如`n+1`),表示当前还无法凑成该金额。 ##### 4. 遍历顺序 由于每个`dp[i]`的值依赖于比它小的`dp[j]`(`j < i`),因此我们需要从`dp[1]`开始,逐步计算到`dp[n]`。 ##### 5. 结果判断 最后,如果`dp[n]`仍然是初始大值,则表示无法凑成目标金额,返回-1;否则,返回`dp[n]`作为最少硬币个数。 #### 四、代码实现 ```python def coinChange(coins: List[int], amount: int) -> int: # 初始化dp数组 dp = [float('inf')] * (amount + 1) dp[0] = 0 # 动态规划计算 for i in range(1, amount + 1): for coin in coins: if i >= coin and dp[i - coin] != float('inf'): dp[i] = min(dp[i], dp[i - coin] + 1) # 判断是否可以凑成目标金额 return dp[amount] if dp[amount] != float('inf') else -1 # 示例 coins = [1, 2, 5] amount = 11 print(coinChange(coins, amount)) # 输出: 3 ``` #### 五、优化思考 虽然上述动态规划解法已经相当高效,但在某些极端情况下(如硬币种类极多但金额较小),我们可能还可以进一步优化。例如,通过预处理某些信息来减少不必要的计算,或者采用更高级的数据结构(如线段树、树状数组等)来优化查询效率。然而,这些优化通常基于特定场景,且实现复杂度较高,需要根据实际问题灵活选择。 #### 六、总结 零钱兑换问题是一个典型的动态规划应用实例,通过定义状态、构建状态转移方程、合理初始化以及正确的遍历顺序,我们可以高效地解决这类问题。在面试中,熟练掌握这类问题的解法不仅能展现你的算法能力,还能体现你对动态规划思想的深刻理解。希望本讲的内容能对你有所帮助,祝你在算法面试中取得优异成绩!
上一篇:
49 | 面试题:最长上升子序列
下一篇:
51 | 面试题:编辑距离
该分类下的相关小册推荐:
数据结构与算法(下)
业务开发实用算法精讲
数据结构与算法之美
编程之道-算法面试(上)
编程之道-算法面试(下)
数据结构与算法(中)
数据结构与算法(上)