快速通道
前言
这个月是动态规划月呀,动态规划的题目核心就是找到状态转移方程。零钱兑换这个系列是很经典的背包题,就是总容量,然后往里边放固定大小的物品,有的是返回方案数,有的是返回最少物品的个数。
一定要看
一篇文章吃透背包问题!(细致引入+解题模板+例题分析+代码呈现)
322. 零钱兑换
给定不同面额的硬币 coins 和一个总金额amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
来源:力扣(LeetCode)
理解
这种题目乍一看很像贪心算法题,先取大的放,不过不行再放小的。不过如果不能正好放下的话,就需要再取小一点的,然后再放重复。这样就类似于回溯算法了,不过这样做执行会超时。
既然超时了,一般回溯(DFS)的做法就是记忆化存储,就是疯狂剪枝。
不过这边就不这样做了,熟悉这种题型的就能判断该题就是背包问题中返回最小物品的个数。
解题
动态规划题型一般都需要一个数组来存储前面的数,然后通过状态转移方程来获取下一个数
func coinChange(coins []int, amount int) int {
// 背包问题·
dp := make([]int, amount+1)
for i:=1; i<=amount; i++ {
dp[i] = amount + 1
for _, coin := range coins {
if i >= coin && dp[i - coin] + 1 < dp[i] {
dp[i] = dp[i - coin] + 1
}
}
}
if dp[amount] == amount + 1 {
return -1
}
return dp[amount]
}
518. 零钱兑换 II
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
来源:力扣(LeetCode)
理解
这一题跟之前做过的 377. 组合总和 Ⅳ 很相似 -->
【举一反三】力扣刷题-组合总和(Python 实现)当时这边还是用的回溯+剪枝做的,回头再看看。
说回这一题,这一题也是求组合数,只不过这一题不能有重复的组合,或者说那一题是有序的,所以可以存在重复的组合。
解题
如何保证没有重复的组合呢,那就想办法让组合是单调递增即可,这个地方实现起来稍微麻烦,我要保证我的下一个数不比前一个数小,那我应该让小的数先排。 看实现,先循环的coins,然后再维护dp数组,这个地方很巧妙。开头我放一下力扣上一位大佬写的总结,这位大佬总结了各种背包问题。
func change(amount int, coins []int) int {
// 动态规划
dp := make([]int, amount + 1)
dp[0] = 1
for _,coin := range coins {
for i:=coin;i<=amount;i++ {
dp[i] += dp[i - coin]
}
}
return dp[amount]
}
小试牛刀
讲解: