easy
Go
func lastStoneWeightII(stones []int) int {
sum := 0
for _, stone := range stones {
sum += stone
}
target := sum / 2
dp := make([]int, target+1)
for _, stone := range stones {
for j := target; j >= stone; j-- {
dp[j] = max(dp[j], dp[j-stone]+stone)
}
}
return sum - dp[target] - dp[target]
}
视频跳过二维直接讲一维,也没讲为什么遍历顺序也要和01背包一样。这题看文字,视频和文字的扩展部分是一样的,可以用来方便理解。
- 先说01背包有几种方法
dp[i][j]:从下标为 [0,i] 的物品里任意取,凑满容量为 j 的背包,有dp[i][j]种方法。
一共两个状态:不放物品i ; 放物品i
dp[i][j] = dp[i-1][j] + dp[i-1][j-cap[i]]
同样地,滚动数组。
- 为什么容量要 倒序?
因为我们是要用的其实是"上一行 dp[i-1][·] 的值",
倒序可以保证 dp[j-cap[i]] 还没被当前物品覆盖,仍然是"上一轮的状态"。
- 为什么遍历顺序不能先背包?
dp[j-cap] 还没更新,不是上一层 dp,而是初始 dp → 无法组合物品。
例如,dp[5] 那一轮已经过去了,不会再用"更新后的 dp[2] = 3"来算一次。
dp[j] = dp[j] + dp[j - cap[i]],即:dp[j] += dp[j - cap[i]]
- 本题是01背包有几种方法的应用
- 正数集合a+负数集合b=sum
- a-b=target
则 a=sum+target/2
Go
import "math"
func findTargetSumWays(nums []int, target int) int {
sum := 0
for _, num := range nums {
sum += num
}
if math.Abs(float64(target)) > float64(sum) {
return 0
}
if (sum+target)%2 == 1 {
return 0
}
bagSize := (sum + target) / 2
dp := make([]int, bagSize+1)
dp[0] = 1
for _, num := range nums {
for j := bagSize; j >= num; j-- {
dp[j] += dp[j-num]
}
}
return dp[bagSize]
}
原本的01背包dp[j]是容量为 j。本题的容量有两个维度:最多 有 m 个 0 和 n 个 1,也就是理解成背包的限制条件从一维的容量 -> 二维的重量和体积。那么就要定义成dp[j][k]。
本题求的是物品的个数,也就是把 +value[i] 换成 +1
Go
func findMaxForm(strs []string, m int, n int) int {
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
for _, str := range strs {
x, y := 0, 0
for _, val := range str {
if val == '0' {
x++
} else {
y++
}
}
for j := m; j >= x; j-- {
for k := n; k >= y; k-- {
dp[j][k] = max(dp[j][k], dp[j-x][k-y]+1)
}
}
}
return dp[m][n]
}