算法 - 动规04(Swift版本)

81 阅读3分钟

题目1:1049.最后一块石头的重量II

讲解
leetcode

本题属于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.目标和

讲解
leetcode

想不到呀想不到~
理解: 首先每个元素前 带 + 或者 -。
那么就是 一部分正数组合(可能有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. 一和零

讲解
leetcode

多维度背包,最多装多少个物品。

// @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