动态规划:416.分割等和子集(0-1背包)

2021-03-16  本文已影响0人  zmfflying

/**

题目

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:

每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].

示例 2:

输入: [1, 2, 3, 5]

输出: false

解释: 数组不能分割成两个元素和相等的子集.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

测试代码

print(canPartition([1, 5, 11, 5]))

笔记

这道题是经典的 0-1背包 题型
每个元素只能取一次, 每次都是在取和不取中寻求最优值
具体步骤见代码和注释

关于 0-1背包, 可以参考这篇文章:
https://juejin.cn/post/6844903607855251463

代码地址

https://github.com/zmfflying/ZMathCode
*/

解题代码

import Foundation

//标准的 0-1 背包
func canPartition(_ nums: [Int]) -> Bool {
    var sum = 0
    for num in nums {
        sum += num
    }
    
    //这道题的一个特点就是: 如果和不是偶数就肯定不会符合条件
    //因为数组 nums 只包含正整数, 而奇数除以2就有小数了
    if sum % 2 != 0 {
        return false
    }

    //分割成 0-1 背包问题就是: 在 nums 里面, 寻找能满足和等于数组总和一半的子集
    let C = sum / 2
    let count = nums.count
    
    //二维数组 dp[i][j] 表示: 在前 i 个元素里, 是否存在和等于 j 的子集
    //注意这里 内层数组个数为 C+1, 因为 C 的情况也要取
    //外层数组个数为 count即可, 因为 count 用不上, 最后一个元素是 count-1
    var dp: [[Bool]] = [[Bool]].init(repeating: [Bool].init(repeating: false, count: C+1), count: count)
    
    //设置初始值: dp[i][0] 恒等于 true, 因为和等于 0 的子集肯定存在, 即 []
    for i in 0..<count {
        dp[i][0] = true
    }
    
    for i in 1..<count {
        let num = nums[i]
        for j in 1...C {
            //如果比j大, 那肯定不选当前元素
            if j < num {
                dp[i][j] = dp[i-1][j]
            } else {
                //选 i 的情况是 dp[i-1][j-num]
                //不选 i 的情况是 dp[i-1][j]
                //因为都是 bool 类型, true || false = true
                //所以只要有一种满足, 那就可以了
                dp[i][j] = dp[i-1][j] || dp[i-1][j-num]
            }
        }
    }

    return dp[count-1][C]
}

//优化 一维数组
//针对本题而言,dp里是bool类型元素,只要有一个为true
//那么 true || false = true
func canPartition1(_ nums: [Int]) -> Bool {
    var sum = 0
    for num in nums {
        sum += num
    }

    if sum % 2 != 0 {
        return false
    }

    let C = sum / 2
    let count = nums.count

    var dp: [Bool] = [Bool].init(repeating: false, count: C+1)
    dp[0] = true
    
    for i in 1..<count {
        let num = nums[i]
        //注意这里要从大到小
        //因为要避免后面的元素收到前面的影响
        //如果是从小到大, 那么如果 dp[0] = true, j 一次遍历后, 所有 d[0+num] 的元素都为true了
        var j = C
        while j >= num {
            dp[j] = dp[j] || dp[j-num]
            j -= 1
        }
    }

    return dp[C]
}

上一篇下一篇

猜你喜欢

热点阅读