1、01背包
题目
有N件物品和一个容量为V的背包。第i件物品的体积是w[i],价值是c[i]。求解将哪些物品装入背包可使价值总和最大。
解
设dp[j]为当背包容量为j时,当前背包能达到的最大价值。
那么对于一个物品,如果它没有超重,我可以拿或者不拿,有以下两种情况:
- 如果拿: (dp[j-w[i]] + c[i]) 当前的价值为dp[当前背包重量-当前物品重量] + 当前物品价值,也就是dp[j-w[i]] + c[i]
dp[j-w[i]]表示,如果我拿了这个物品,剩余容量能够达到的最大价值。
- 不拿: dp[j] 如果不拿,就代表容量不变,价值也不变
对于这个物品,在拿和不拿的情况下,我需要达到最大价值,就需要取这两种情况的最大价值. 也就是
dp[j] = max(dp[j-w[i]] + c[i], dp[j])
那么对于这个问题,可以这样解决:
fun main() {
// 物品的重量
val ws = intArrayOf(9, 2, 1)
// 物品的价值
val cs = intArrayOf(100, 2, 3)
// 背包的重量
val v = 10
// 定义dp数组,dp[j]为当背包容量为j时,当前背包能达到的最大价值。
val dp = IntArray(v + 1)
// 遍历我的物品
for (i in 0..(ws.size - 1)) {
// 从容量逆序到当前物品重量, 逆序是因为dp[j] 需要用到dp[j - ws[i]], j - ws[i]肯定比j小
// 所以为了避免覆盖dp[j - ws[i]]的值, 需要先逆序先算大的
for (j in v downTo ws[i]) {
// 拿和不拿取大
dp[j] = max(dp[j - ws[i]] + cs[i], dp[j])
}
}
// 输出容量为v时能达到的最大价值
println(dp[v])
}
2、零钱兑换
题目
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
解
使用一个dp数组, dp[j]表示当金额为i时,能够使用的最少硬币.
dp[0]的初始值为0,因为0无法用任何硬币凑成,方案数量为0.
对于一个金额j,我们可以用 j 减去当前硬币的面值的最小数量加上当前面值的一枚硬币, 也就是dp[j - coin] + 1, 遍历所有面值的硬币,取其最小值就是当前金额所需数量的最小值
dp[j] = min(dp[j-coin1] + 1, dp[j-coin2] + 1, dp[j-coin3] + 1, .........)
最终代码如下:
fun main() {
// 硬币
val coins = intArrayOf(1, 3, 4)
val amount = 7
val dp = IntArray(amount + 1) { amount + 1 }
// 凑齐0元有0种方法
dp[0] = 0
// 硬币是无限的,所以先遍历金额
for (i in 1..amount) {
// 遍历硬币
for (coin in coins) {
// 硬币的面额必须小于等于金额
if (coin <= i) {
// 对于一个面额,我可以用 (i 减去当前面额的面值)所需要的最小数量 + 1(当前面额) 和当前面额所需硬币最小数量 对比
dp[i] = min(dp[i - coin] + 1, dp[i])
}
}
}
// 如果最小数量为amount + 1,表示凑不到, 返回-1
println(if (dp[amount] == amount + 1) -1 else dp[amount])
}
零钱兑换Ⅱ
题目
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
解
dp[j] 表示凑齐j元的方案. 凑齐j元的方案可以加上(凑齐j - 硬币面额的方案), 遍历所有面额的硬币,一直累加.
硬币放在外层循环,保证了凑齐的方案不会重复: 比如先用一元面额的硬币凑:
dp[1] += dp[0] // 1
dp[2] += dp[1] // 1 1
dp[3] += dp[2] // 1 1 1
.....
再用两元面额的硬币凑
dp[2] += dp[0] // 2
dp[3] += dp[1] // 1 2
dp[4] += dp[2] // 1 1 2
再用三元面额的硬币凑
dp[3] += dp[0] // 3
dp[4] += dp[1] // 1 3
dp[5] += dp[2] // 1 1 3 和 2 3
最终代码:
fun main() {
// 硬币
val coins = intArrayOf(1, 2, 3)
val amount = 5
val dp = IntArray(amount + 1)
dp[0] = 1
for (coin in coins) {
for (i in 1..amount) {
if (i >= coin) {
dp[i] += dp[i - coin]
}
}
}
println(dp[amount])
}