算法 - 动规03(Swift版本)

86 阅读3分钟

题目1:背包问题

背包二维解法

func bagValue(_ weight: [Int], _ value: [Int], _ bagSize: Int) -> Int {
    // bagSize 背包容量
    // weight  物品重量
    // value   物品价值
    guard weight.count > 0, value.count > 0, bagSize > 0 else { return 0 }
    // dp含义: dp[i][j] 背包容量为j,可以拿最多i件物品,可以装的最大价值
    var dp = Array(repeating: Array(repeating: 0, count: bagSize + 1), count: weight.count)
    // 第一列初始化: 背包容量为0时 结果均为0
    for i in 0..<weight.count { dp[i][0] = 0 }
    // 第一行初始化: 只取第一件物品,
    for i in 0...bagSize {
        if i >= weight[0] {
            dp[0][i] = value[0]
        }
    }
    // 递推 先遍历物品, 后遍历背包
    for i in 1..<weight.count {
        for j in 0...bagSize {
            // 当前物品比背包大,指定放不进去。
            if weight[i] > j {
                dp[i][j] = dp[i - 1][j]
            } else {
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
            }
        }
    }
    return dp[weight.count - 1][bagSize]
}

print("\(bagValue([1, 3, 4], [15, 20, 30], 4))") // 35
print("\(bagValue([2, 2, 3, 1, 5, 2], [2, 3, 1, 5, 4, 3], 1))") // 5

题目2:背包问题

背包一维解法

func bagValue(_ weight: [Int], _ value: [Int], _ bagSize: Int) -> Int {
    // 所谓一维推导,其实就是只保留一行的数据,不断的遍历更新这一行信息。不用保留前面的信息即可。
    // 所以判断是否可以使用一个维度的判断标准可以理解为:
    // 1. 状态转移公式只依赖于上一层的状态。不依赖于更早的状态。
    // 2. 状态转移的顺序满足递推的需求。如背包问题中从右向左遍历,保证 dp[j] 的当前状态不影响后续计算。

    // dp定义: dp[j] 代表容量为j的背包 可以装载的最大价值。
    var dp = Array(repeating: 0, count: bagSize + 1)

    for i in 0..<weight.count {
        for j in (0...bagSize).reversed() {
            if j < weight[i] { break }
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
        }
    }
    return dp[bagSize]
}

print("\(bagValue([1, 3, 4], [15, 20, 30], 4))") // 35
print("\(bagValue([2, 2, 3, 1, 5, 2], [2, 3, 1, 5, 4, 3], 1))") // 5

题目3:416.分割等和子集

讲解
leetcode

// 回溯做法: 没有AC,超时了。
class Solution {
    func canPartition(_ nums: [Int]) -> Bool {
        let nums = nums.sorted()
        let totalSum = nums.reduce(0, +)
        if totalSum % 2 != 0 { return false }
        print("\(totalSum)")
        var set = [Int]()
        var sum = 0
        func recursive(_ nums: [Int], _ start: Int) -> Bool {
            print("\(set)")
            if start == nums.count { return false } 
            for i in start..<nums.count {
                if sum + nums[i] == totalSum / 2 { return true }
                if sum + nums[i] > totalSum / 2 { break }
                set.append(nums[i])
                sum += nums[i]
                if recursive(nums, i + 1) { return true }
                sum -= nums[i]
                set.removeLast()
            }
            return false
        }
        if recursive(nums, 0) { return true }
        return false
    }

}

// 动规01背包思路
// @lc code=start
class Solution {
    // 套用背包问题 还是要绕一些的。
    // 物品即为nums 元素, 价值 和 体积 都为nums里面的值。
    // 背包体积为 sum / 2
    // 虽然dp含义是 能装下的最大值, 但是如果dp[i] == i, 那一定已经是能装下的最大价值了。 因为体积和价值是相等的,这里已经装了价值为i的物品,说明体积总和也是i了。
    func canPartition(_ nums: [Int]) -> Bool {
        let sum = nums.reduce(0, +)
        if sum % 2 != 0 { return false }
        let target = sum / 2
        var dp = Array(repeating: 0, count: target + 1)
        for i in 0..<nums.count {
            for j in (0...target).reversed() {
                if nums[i] > j { break }
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
            }
        }
        return dp[target] == target
    }

}
// @lc code=end