322.零钱兑换 五种方法讲解

文章目录

  • 322.零钱兑换 五种方法讲解
    • 1 问题描述
    • 2 问题分析
    • 3 解决策略
      • 3.1 递归-暴力解决
    • 3.2 递归-加入存储
    • 3.3 BFS
    • 3.4 动态规划-自上而下
    • 3.5 动态规划-自下而上

1 问题描述

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

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

示例 1:

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

2 问题分析

给人的第一直接就是让程序去尝试所有的可能性,然后将其中所需硬币数量最少的结果进行返回。当需要尝试所有可能性的时候,我们往往会用到树(Tree)这种数据结构。我们假设以coins=[1,2,5], amount=11为例子,试图去用树对这个问题进行建模。我们假定每个节点的数字代表的是当前的金额。

可以看到,从根节点11出发,我们有三个硬币,所以有三个分支,而每一个金额又可以对应新的三个节点,如果节点的值为0,也就是说当前的金额就是0,说明任务完成了,我们成功凑出了金额,如果小于0,那就说明当前分支不能够凑出该金额。

3 解决策略

3.1 递归-暴力解决

根据上面的分析,发现这个问题很容易可以被拆解成递归的方式去解决,每个子问题就是找到当前金额所需的最少硬币数量,如果当前金额为0,那么就返回当前硬币的数量,这是递归的退出条件之一,除此之外,当金额小于0时,也需要返回值。考虑到所需要的硬币数量不可能大于amount + 1,在金额小于0时,说明不可以凑成我们想要的数,那么我们就返回一个amount + 1来表示当前的分支是不能凑成所需金额的。

class Solution(object):def coinChange(self, coins, amount):def helper(coins, curAmount, numOfCoins):globalMin = amount + 1if curAmount == 0:return numOfCoinsif curAmount < 0:return amount + 1for coin in coins:curMin = helper(coins, curAmount - coin, numOfCoins + 1)globalMin = min(curMin, globalMin)return globalMinreturn helper(coins, amount, 0)

时间复杂度:O(nS)O(n^S)O(nS), 其中 S 是要凑的金额数amount,而 n 是所拥有的硬币数量。这是因为考虑到树结构最多的层数不会超过SSS,而分支的数量则取决于硬币的个数。
空间复杂度:O(S)O(S)O(S),因为至多需要存储SSS个值来得到amount

3.2 递归-加入存储

如果细心的话会发现,在计算过程中有很多重复计算的部分,所以我们可以加入存储,如果当前的金额已经被计算出来,那我们就直接返回结果即可。

class Solution(object):def coinChange(self, coins, amount):self.cache = {}def helper(coins, curAmount):if curAmount in self.cache:return self.cache[curAmount]globalMin = amount + 1if curAmount == 0:return 0if curAmount < 0:return amount + 1for coin in coins:curMin = helper(coins, curAmount - coin)if curMin == globalMin:continueglobalMin = min(curMin + 1, globalMin)self.cache[curAmount] = globalMinreturn globalMinresult = helper(coins, amount)return -1 if result == amount + 1 else result

这样做之后时间复杂度就可以减少到O(Sn)O(Sn)O(Sn),这是因为每一次的计算步骤至多需要SSS次运算,而我们每次都需要计算所有不同面额的硬币的可能性,所以一共需要计算两者的乘积。

3.3 BFS

上述两种递归方法更偏向于深度优先遍历,但是就我们的问题而言,其实并不需要这样做,实际上,如果使用BFS遍历的方法,当我们找到第一次amount为0的时候,就已经代表我们找到了结果,因为当前树的层数一定是最少的。

import collections
class Solution(object):def coinChange(self, coins, amount):q = collections.deque([(amount, 0)])seen = set([amount])while q:curAmount, numOfCoins = q.popleft()if curAmount == 0:return numOfCoinsfor coin in coins:if curAmount - coin >= 0 and curAmount - coin not in seen:q.append((curAmount - coin, numOfCoins + 1))seen.add(curAmount - coin)return -1

3.4 动态规划-自上而下

如果从动态规划的角度来考虑问题,还是以老例子为例,想要求解F(11)F(11)F(11),那其实就是求解min(F(11−coin1)+1,F(11−coin2)+1,F(11−coin3)+1)min(F(11-coin_1)+1, F(11-coin_2)+1,F(11-coin_3)+1)min(F(11−coin1​)+1,F(11−coin2​)+1,F(11−coin3​)+1),而当中的每一项都可以被继续分解,直到F(0)F(0)F(0)。而F(0)F(0)F(0)的意思是凑出0元所需的硬币个数,结果显然是0。

class Solution(object):def coinChange(self, coins, amount):seen = {}def helper(curAmount):if curAmount in seen:return seen[curAmount]if curAmount == 0:return 0if curAmount < 0:return amount + 1minNumOfCoins = amount + 1for coin in coins:minNumOfCoins = min(helper(curAmount - coin) + 1, minNumOfCoins)seen[curAmount] = minNumOfCoinsreturn minNumOfCoinsresult = helper(amount)return result if result != amount + 1 else -1

3.5 动态规划-自下而上

有了上面的思路,我们同样可以从反方向来考虑这个问题,先给定F(0)F(0)F(0),然后再去求解F(1)F(1)F(1),F(2)F(2)F(2),以此类推,直到F(amount)F(amount)F(amount)为止。

import collections
class Solution(object):def coinChange(self, coins, amount):q = collections.deque([(amount, 0)])seen = set([amount])while q:curAmount, numOfCoins = q.popleft()if curAmount == 0:return numOfCoinsfor coin in coins:if curAmount - coin >= 0 and curAmount - coin not in seen:q.append((curAmount - coin, numOfCoins + 1))seen.add(curAmount - coin)return -1

[LeetCode] 322.零钱兑换 五种方法讲解相关推荐

  1. leetcode: 322.零钱兑换

    322.零钱兑换 来源:力扣(LeetCode) 链接: https://leetcode.cn/problems/coin-change/ 给你一个整数数组 coins ,表示不同面额的硬币:以及一 ...

  2. LeetCode 322. 零钱兑换

    322. 零钱兑换 难度 中等 给你一个整数数组 coins ,表示不同面额的硬币:以及一个整数 amount ,表示总金额. 计算并返回可以凑成总金额所需的 最少的硬币个数 .如果没有任何一种硬币组 ...

  3. LeetCode 322. 零钱兑换(DP)

    文章目录 1. 题目信息 2. 解题 2.1 回溯穷举 2.2 动态规划 1. 题目信息 给定不同面额的硬币 coins 和一个总金额 amount. 编写一个函数来计算可以凑成总金额所需的最少的硬币 ...

  4. Java实现 LeetCode 322 零钱兑换

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

  5. golang力扣leetcode 322.零钱兑换

    322.零钱兑换 322.零钱兑换 题解 代码 322.零钱兑换 322.零钱兑换 题解 //state: dp[i]金额为i时所需最少硬币个数 //function: dp[i]=dp[i-n]+1 ...

  6. Leetcode.322 零钱兑换

    索引iii表示 amountamountamount 金额,dp[i]dp[i]dp[i] 表示最少 coinscoinscoins 个数. 递归 class Solution {int res = ...

  7. Leetcode 322.零钱兑换

    Time: 20190906 Type: Medium 题目描述 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币 ...

  8. leetcode 322. 零钱兑换 思考分析

    目录 1.题目 2.思路分析 3.参考链接 1.题目 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币组合能组成总 ...

  9. 99. Leetcode 322. 零钱兑换 (动态规划-完全背包)

    步骤一.确定状态: 确定dp数组及下标含义 dp数组是个amout+1的数组,dp[j]表示的是装满容量为j的背包所需要的最 少物品的个数 步骤二.推断状态方程: 对于当前物品i, 有两种选择决定了d ...

最新文章

  1. SDH/E1/T1/E3/T3/STM/TDM相关名词介绍
  2. 深度学习之好的博客文章
  3. CISCO SECURE ACCESS CONTROL SERVER
  4. 004-2-拟合,drop-out
  5. elasticsearch mapping之fields
  6. 遇上浏览器跨域问题怎么办?
  7. Bulma - 基于 Flexbox 的现代化的 CSS 框架
  8. 【R图秀-6】地震来了
  9. 【Unity3D基础2-2】认识Unity3D引擎
  10. deepinv2 添加打印机_Deepin系统上安装使用HP惠普打印机的方法
  11. 【计算理论】计算理论总结 ( 非确定性有限自动机 NFA 转为确定性有限自动机 DFA ) ★★
  12. antd表格分页设置
  13. H265 CTU、CU、PU、TU划分的特点及要求
  14. fiddler--HTTP协议调试工具
  15. 正则表达式匹配,2位数字,单个字符-华图在线试题修改
  16. 计算机网络的安全性在哪里,计算机网络面临的安全性威胁可分为哪两类分别包含..._安全工程师_帮考网...
  17. 鸿蒙系统别的手机厂商可以用,鸿蒙有望适配国内其他手机厂商,别再黑了
  18. woo语言实现 m3u8流媒体视频文件 下载并播放
  19. Android R模块编译framework
  20. 数据通信与网络(五)

热门文章

  1. 跳子游戏--最少跳跃次数
  2. 超级无敌伪装技之User-Agent
  3. CDbCriteria示范代码之一
  4. 【XAI】Adversarial Defense Framework for Graph Neural Network
  5. Mobox 知识管理平台助推市长质量奖
  6. 如何使用开发者工具?
  7. 【清华夏令营2016模拟5.31】图样
  8. 统计1到N的整数中,除了1和自身之外,至少还能被两个数整除的数的个数Java开方平方根
  9. 浙大MBA提前批面试有多难?
  10. django问题详解