题目1:1049.最后一块石头的重量II
本题属于01背包的应用题, 不过反过来往01背包上套的时候,还是需要转几个弯, 下面捋下思路。
和 416.分割等和子集 题目非常相似。
这里的背包容量 就是总重量的一半。
物品价值和重量 是一样的,这里就是石头的重量。
另外需要理解的一点是: dp[i] 代表的是容量为i的背包 可以装下的最大价值(最大重量)。
对于本题来说 容量为重量总和一半的背包,这个装下的最大价值,的确就是差值最小的情况了。
对于 416.分割等和子集 来说,如果容量为总和一半的背包,如果可以装满,那么就是可以分割成两个相等子集了。
// @lc code=start
class Solution {
func lastStoneWeightII(_ stones: [Int]) -> Int {
let sum = stones.reduce(0, +)
let target = sum / 2
var dp = Array(repeating: 0, count: target + 1)
for i in 0..<stones.count {
for j in (0...target).reversed() {
if j < stones[i] { break }
dp[j] = max(dp[j], dp[j - stones[i]] + stones[i])
}
}
return sum - 2 * dp[target]
}
}
// @lc code=end
题目2:494.目标和
想不到呀想不到~
理解:
首先每个元素前 带 + 或者 -。
那么就是 一部分正数组合(可能有0) 一部分负数组合。
sum 正 - sum 负 = target
sum 正 + sum 负 = sum
最后可以推算下来 sum正 = (target + sum) / 2。
那么也就是正数集合总和等于这个数 即可认为符合题目要求了。
然后套到背包问题上:就是用nums装满 背包(容量为sum正), 有多少种装法。
所以此题也抽象成为一个经典类型叫做:
用物品装满背包(容量为X),有多少种装法?
// 二维解题
class Solution {
// 相当难想,理解题解即可
func findTargetSumWays(_ nums: [Int], _ target: Int) -> Int {
let sum = nums.reduce(0, +)
// 这里注意取绝对值
if abs(target) > sum { return 0 }
if (target + sum) % 2 != 0 { return 0 }
let size = (target + sum) / 2
var dp = Array(repeating: Array(repeating: 0, count: size + 1), count: nums.count)
// 初始化第一行
if nums[0] <= size { dp[0][nums[0]] = 1 }
// 初始化第一列
// 这里也可以选择不用初始化第一列:直接用下面这行代替
// if nums[0] == 0 { dp[0][0] = 2 } else { dp[0][0] = 1 }
var initval = 1
for i in 0..<nums.count {
if nums[i] == 0 {
initval *= 2
}
dp[i][0] = initval
}
for i in 1..<nums.count {
// 必须从0开始,否则0,0,0,0 的case无法通过
for j in 0...size {
// 放不下,只能选择不放。
if j < nums[i] {
dp[i][j] = dp[i - 1][j]
continue
}
// 不放 + 放
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]]
}
}
return dp[nums.count - 1][size]
}
}
// 一维解法
// 转化过程当中唯一的困惑是初始化的部分:
// 首先省略了初始化第一列:实际上二维的第一列初始化也是非必要的。再上面解释了。
// 其次省略了第一行, 其实是总第一行开始滚动的,第一行的初始化 相当于是被算出来的。
// 包括 0,0,0,0 这种case 第一行第一个也是会被改写成2的。
class Solution {
// 相当难想,理解题解即可
func findTargetSumWays(_ nums: [Int], _ target: Int) -> Int {
let sum = nums.reduce(0, +)
// 这里注意取绝对值
if abs(target) > sum { return 0 }
if (target + sum) % 2 != 0 { return 0 }
let size = (target + sum) / 2
var dp = Array(repeating: 0, count: size + 1)
dp[0] = 1
for i in 0..<nums.count {
// 必须从0开始,否则0,0,0,0 的case无法通过
for j in (0...size).reversed() {
if j < nums[i] {
break
}
dp[j] += dp[j - nums[i]]
print("\(i) \(j) \(dp[j])")
}
}
return dp[size]
}
}
题目3: 474. 一和零
多维度背包,最多装多少个物品。
// @lc code=start
class Solution {
func findMaxForm(_ strs: [String], _ m: Int, _ n: Int) -> Int {
var dp = Array(repeating:Array(repeating: 0, count: n + 1), count: m + 1)
dp[0][0] = 0
for str in strs {
var zero = 0, one = 0
for char in str {
if char == "0" {
zero += 1
} else {
one += 1
}
}
for i in (0...m).reversed() {
if i < zero { break }
for j in (0...n).reversed() {
if j < one { break }
dp[i][j] = max(dp[i][j], dp[i - zero][j - one] + 1)
}
}
}
return dp[m][n]
}
}
// @lc code=end