最近在LeetCode上刷算法题,准备秋招。刷了一些题之后,发现有些题非常棒,能够将多种知识点结合在一起。本文就以“零钱兑换”问题为例,展示同一道题的多种不同解法。

零钱兑换问题

解法1:贪心法 + BFS

贪心法的思路是每次都优先选择可以选择的最大面额的硬币。

比如硬币的面额分别是:[1, 2, 5];需要兑换的零钱是:11。

按照贪心法的思路,优先选择 2个面额为 5 的硬币,然后剩下的零钱是 11 - 2 * 5 = 1。然后再选择 1 个面额为 1 的硬币,剩下的零钱是 1 - 1 * 1 = 0。解决问题!最终得到的结果是 3。可以验证答案是正确的!

贪心法的思路很直观,而且大部分情况下能够正确解决问题。但可惜对于某些特殊情况,贪心法得到的答案是错误的。

比如硬币的面额分别是:[1, 7, 10];需要兑换的零钱是:14。

按照贪心法的思路,优先选择 1 个面额为 10 的硬币,然后剩下的零钱是 14 - 10 = 4,只能选择 4 个面额为 1 的硬币。最终得到的结果是需要 5 个硬币。可以验证,这不是正确答案。正确答案是选择 2 个面额为 7 的硬币。

贪心法思路简单、直接,但不能保证答案是正确的。那贪心法就没有了吗?能不能想想办法,保证使用贪心法时能够找到正确答案呢?有的!答案就是:贪心法 + 深度优先搜索(DFS)。

我们可以使用贪心法的思想,每次优先选择可能的最大面额的硬币。但在此之上,为了保证得到正确解,我们需要按深度优先搜索的方法,遍历整个递归状态树。不过,我们可以加上一些剪枝条件,加快搜索速度。

具体解法如下:

C++解法:class Solution {

public:

int coinChange(vector& coins, int amount) {

if (amount <= 0 || coins.empty())   return 0;

// 对币值按照从大到小的顺序排序

sort(coins.rbegin(), coins.rend());

int ans = INT_MAX;

backtrack(coins, amount, 0, 0, ans);

return ans == INT_MAX ? -1 : ans;

}

private:

void backtrack(vector &coins, int amount, int idx, int count, int & ans) {

// 递归终止条件

if (amount == 0) {

ans = min(count, ans);  // 保证得到的是正确答案

return;

}

if (idx == coins.size())    return;

// count + k

for (int k = amount / coins[idx]; k >= 0 && count + k

// k * coins[idx] 加快搜索速度

backtrack(coins, amount - k * coins[idx], idx + 1, count + k, ans);

}

};

Python解法class Solution:

def coinChange(self, coins: List[int], amount: int) -> int:

if amount <= 0 or not coins: return 0

// 对币值按照从大到小的顺序排序

coins.sort(reverse=True)

self.res = amount + 1

self.dfs(coins, amount, 0, 0)

return -1 if self.res == amount + 1 else self.res

def dfs(self, coins, amount, idx, count):

// 递归终止条件

if amount == 0:

self.res = min(self.res, count)  // 保证得到的是正确答案

return

if idx >= len(coins):

return

k = amount // coins[idx]

// count + k

while k >= 0 and count + k

// k * coins[idx] 加快搜索速度

self.dfs(coins, amount - k * coins[idx], idx + 1, count + k)

k -= 1

解法2:动态规划

动态规划是这道题最常见的解法。利用递归的思想,要想知道凑成零钱 amount 的最少硬币数量,只需要知道所有 amount - coin[i] 对应的零钱需要的最少硬币数量,然后求出最少的那个数量再加上 1 即可。然后因为中间的零钱数量可能重复,因此需要采用一个数组保存中间结果。具体解法如下:

Pythonclass Solution:

def coinChange(self, coins: List[int], amount: int) -> int:

if amount <= 0: return 0

# dp 数组存储中间结果,初始化为最大值

dp = [(amount + 1)] * (amount + 1)

dp[0] = 0

# 从 1 开始计算各个零钱数需要的硬币数量

for i in range(1, amount + 1):

for c in coins:

if i - c >= 0:

dp[i] = min(dp[i], dp[i - c] + 1)

return -1 if dp[-1] == amount + 1 else dp[-1]

解法3:广度优先搜索

除了以上两种解法,我们还可以使用广度优先搜索(BFS)。如下图所示,我们可以将零钱根据硬币面额展开成一棵多叉树,其中多叉树的每个分支对应硬币的面额。然后使用广度优先搜索,当扩展出来的节点值为 0 时,表示已经完成兑换,多叉树当前的层次就是结果。

零钱根据Coin面额展开的多叉树

Pythonclass Solution:

def coinChange(self, coins: List[int], amount: int) -> int:

if amount <= 0: return 0

count = 0

# 从 amount 开始递减

value1, value2 = [amount], []

visited = [False] * amount + [True]

while value1:

count += 1

for val in value1:

for coin in coins:

newval = val - coin

if newval >= 0:

if not visited[newval]:

# 终止条件

if newval == 0:

return count

visited[newval] = True

value2.append(newval)

value1, value2 = value2, []

return -1

Pythonclass Solution:

def coinChange(self, coins: List[int], amount: int) -> int:

if amount <= 0: return 0

count = 0

# 从 0 开始累加

value1, value2 = [0], []

visited = [True] + [False] * amount

while value1:

count += 1

for val in value1:

for coin in coins:

newval = val + coin

if newval <= amount:

if not visited[newval]:

# 终止条件

if newval == amount:

return count

visited[newval] = True

value2.append(newval)

value1, value2 = value2, []

return -1

综上所述,解决零钱兑换问题,至少有3种完全不同的解法。其中跟动态规划紧密相连的“递归+记忆化”解法在此并未提及。递归解法是自顶向下的思路,动态规划的自底向上的思路,两者殊途同归。不过可以看出,最常见的动态规划解法其实并不是最优的。以我的提交情况来看,“贪心法+DFS”优于 BFS,BFS 优于动态规划。

你还能想到什么其他解法,欢迎评论交流。

python解决换零钱问题_多种解法解决“零钱兑换”问题相关推荐

  1. python判断当前激活窗口_多种窗口都可能被激活的情况下判断其中某个是否激活...

    遇到的问题 今天测试 SAP 系统流程的时候突然出现了问题导致无法继续,问题是这样的:在 SAP 查询出结果后需要点击导出为电子表格,点击以后过几分钟会弹出这样的窗口: 然后我只需要检测 " ...

  2. python运行完不能显示图_【已解决】Python中通过Image的open之后,去show结果打不开bmp图片,无法正常显示图片...

    [问题] 在windows的cmd命令行下,使用Python的PIL库打开并显示一个jpg图片:openedImg = Image.open(saveToFile); print "open ...

  3. python输出日志到文件_【已解决】Python中,如何让多个py文件的logging输出到同一个日志log文件...

    [问题] 有一个比较长的python脚本文件,其中关于log日志输出,用的是logging,对应初始化代码为:logging.basicConfig( level = logging.DEBUG, f ...

  4. python判断邮件发送成功_【基本解决】python中用SMTP发送QQ邮件提示成功但是收件人收不到邮件...

    折腾: 期间, 已经用了smtp的ssl去发送邮件了,但是结果: 第二收件人也没有收到邮件... 那去把端口号从465改为587: smtpPort=587, 结果直接出错: smtpObj = sm ...

  5. python怎么换背景颜色_用opencv给图片换背景色的示例代码

    图像平滑 模糊/平滑图片来消除图片噪声 OpenCV函数:cv2.blur(), cv2.GaussianBlur(), cv2.medianBlur(), cv2.bilateralFilter() ...

  6. python通过代理访问网页_【已解决】Python中使用代理访问网络

    [问题] 在用Python的urllib2等库,访问网络,发现某些网址访问很慢,比如: 但是,当使用代理(此处用的是gae)后,发现访问速度就快很多了. 所以,希望给Python的访问网络,增加代理的 ...

  7. python 回溯法 01背包问题_回溯法解决01背包问题

    回溯法是一个既带有系统性又带有跳跃性的搜索算法. 它在包含问题的所有解的解空间树中按照 深度优先的策略, 从根节点出发搜索解空间树. 算法搜索至解空间树的任一节点时, 总是先判断 该节点是否肯定不包含 ...

  8. python画大对勾_多种方法告诉你!Word如何在方框中打对勾√和叉叉×

    我们平常在一些金融机构填写表格的时候,都会看到一些表格上带有许多特殊符号,比如,最常见的就是方框中打钩或打叉这种符号.那这种符号是如何输入出来的呢?今天,易老师就来为大家分享几种在方框中打对勾和叉的技 ...

  9. python如何停止子线程_【已解决】python 如何结束子线程呢

    复制内容到剪贴板 代码:# -*- coding:utf-8 -*- import wx import os import sys import configparser import ftpserv ...

最新文章

  1. python flask 如何修改默认端口号
  2. mysql数字前面有0_Mysql中前边有0的数据,0会被舍去的问题
  3. NUCLEUS:13:西门子实时操作系统 Nucleus漏洞影响物联网设备等
  4. Eclipse 启动项目错误:class not found
  5. 微信小程序获取手机号 前台+php后台
  6. 安装Redis教程(详细过程)
  7. 3分钟教你绘制一个围棋棋盘
  8. echarts绘制分时图(1)-- 接收及处理股票接口数据
  9. uni-app小程序使用u-pase富文本解析图片时会出现闪现一下默认的尺寸再变成设置的宽高,有种先大后小的闪现
  10. 【python学习.油价和美元汇率查询】
  11. BHEX创始人巨建华鲲鹏会分享:创业路上从不孤单
  12. 前端常见问题以及处理方式 - - - (九) ES6中的set和map(map篇)
  13. 超级简单的自动刷新_144Hz刷新率真比60Hz快 谁用谁知道
  14. 工厂怎么使用oracle系统,如何用MES系统建立智能工厂?
  15. 20221220英语学习
  16. GCC中 -I、-L、-l 选项的作用
  17. 移动互联下半场,云通信迈向场景为王”
  18. 编译sqlite3时报错【‘GetVersionExA‘: 被声明为已否决】的解决办法
  19. Chromium OS——下载源码4之配置repo
  20. 真的0数学基础0Shader基础,如何通过<Shader入门精要>入门?一些阅读建议。

热门文章

  1. 家暴屡教不改能判刑吗
  2. 新型发明创造大赛计算机类,2019年自主招生的七大途径你知道吗,这些内容你需要了解!...
  3. 金科惊魂830天:城门外的野蛮人与城楼里的危机
  4. 【数据科学】06 数据转换-数据离散化、编码分类特征(哑变量矩阵、数字编码)
  5. 教你用 python 制作一张五彩斑斓的黑
  6. 魔兽世界服务器位面 稳定,魔兽世界:怀旧服70%服务器都在排队,或于减少位面有一定的关系...
  7. 常见的机器学习数据挖掘知识点之Basis
  8. 【自我管理】追求卓越从改变自己做起
  9. sal2edge.py显著性mask生成边缘标签
  10. 20155314 2016-2017-2 《Java程序设计》第8周学习总结