动态规划的原理
- 把大问题拆分成小问题, 通过寻找大问题与小问题的递推关系,解决一个个小问题, 最终达到解决原问题的效果。
- 动态规划通过填写表的方式把已解决的子问题答案记录下来, 在父问题里需要用到子问题时可以直接提取子问题结果, 避免重复计算, 从而节约了时间。 用动态规划解决问题的核心在于填表, 填表完毕,最优解就找到了
背包问题的解决过程
设
表示背包容量,
表示第个物品的价值,
为第个物品的占据背包的容量,
为当前背包容量为j时,前i个物品的最佳组合的价值,
当时,表示第i个物品不装入背包, 当时表示第i个物品装入背包
则问题的数据模型为
- 求
- 约束条件
- 递推关系, 面对物品有两种可能性:
(1) 背包剩余容量装不下当前物品, 此时背包中的物品的价值没变即
(2) 背包剩余容量能装下当前物品, 此时会遇到一个问题, 背包装下当前物品能否达到最优价值, 所以在装与不装之间选择最优答案,即
其中
表示不装当前物品,
表示装下当前物品,使用了背包的容量,同时背包中的价值增加了
例题
有3个物品, 物品的价值、重量和标号相同,有一个背包最大装载重量为4,如何让背包里装入的物品具有最大的价值总和
(物品编号) 1 2 3 (重量) 1 2 3 (价值) 1 2 3
第一步: 画表
顺便初始化边界
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | ||||
| 2 | 0 | ||||
| 3 | 0 |
第二步: 填表
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 1 | 1 | 1 |
| 2 | 0 | 1 | 2 | 3 | 3 |
| 3 | 0 | 1 | 2 | 3 | 4 |
步骤:
从左到右,从上到下
- 当前物品编号为,重量为,价值为, 当前背包容量为
- 若,则
(装不下)
否则
第三步: 得出结论
go语言实现
package main
import (
"fmt"
)
func max(a int, b int) int {
if a > b {
return a
} else {
return b
}
}
func CV() int {
// 物品占用背包容量
w := []int{0, 1, 2, 3}
// 物品价值
v := []int{0, 1, 2, 3}
// 背包空间
capacity := 4
// 动态规划矩阵(表格)
dp := [4][5]int{{0}}
for i := 1; i < len(w); i++ {
for j := 1; j <= capacity; j++ {
if j < w[i] {
// 当前背包空间小于物品占用背包容量
// 则无法装入背包, 当前背包内物品价值等于前i-1个物品
dp[i][j] = dp[i-1][j]
} else {
// 如果能装进背包
// 则取最优解
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i])
}
}
}
return dp[3][4]
}
func main() {
fmt.Print(CV())
}
LeetCode
322. 零钱兑换
状态转移方程 设为总金额, 为金额为时需要的硬币数
func min(arr ...int) int {
minInt := 0x0f0f0f
for i := 0; i < len(arr); i++ {
if minInt > arr[i] {
minInt = arr[i]
}
}
return minInt
}
func coinChange(coins []int, amount int) int {
// dp[i] 表示 面额为i是使用的硬币数
dp := make([]int, amount+1)
for i := 0; i < len(dp); i++ {
dp[i] = amount + 1
}
dp[0] = 0
for i := 1; i <= amount; i++ {
for j := 0; j < len(coins); j++ {
if coins[j] <= i {
// 如果硬币面额小于当前总金额
// 则在取或不取中得到最优解
dp[i] = min(dp[i], dp[i-coins[j]]+1)
}
}
}
if dp[amount] > amount {
return -1
}
return dp[amount]
}