LeetCode 416 - 分割等和子集


文章目录
- 摘要
- 描述
- 题解答案
- 题解代码分析
- 示例测试及结果
- 总结
摘要
这道题看似是一个“集合分割”的问题,但核心本质其实是一个典型的 0/1 背包问题。给定一个整数数组,我们需要判断能否将它分成两个子集,使两个子集的和相等。换句话说,就是看看数组中能否选出一部分数字,使它们的和等于整个数组和的一半。
在这篇文章里,我们不仅会用 Swift 实现完整的解法,还会从动态规划的角度详细拆解思路,并给出可运行的 Demo 示例,帮助你更好地理解这类问题在实战开发中能如何类比使用。

描述
题目要求很明确:
给你一个只包含正整数的非空数组 nums,判断是否可以将其分成两个子集,使得两个子集的元素和相等。
举个例子:
输入: nums = [1,5,11,5]
输出: true
解释: 可以分为 [1,5,5] 和 [11]
如果输入是:
nums = [1,2,3,5]
输出: false
解释: 无法分为两个等和子集。
换句话说,我们要判断是否能在数组中找到一些元素,它们的和恰好等于数组总和的一半。
题解答案
这个问题的本质是:是否存在一个子集,其和为 totalSum / 2。
因此我们可以使用“动态规划”的思路来解决。
假设数组总和是 sum,如果 sum 是奇数,直接返回 false,因为不可能拆成两个整数和相等的部分。
如果是偶数,那么问题就变成:“能否从数组中选出一些数,使得它们的和等于 sum / 2。”
我们可以定义一个布尔型数组 dp[target] 表示:
是否能从
nums的部分元素中选出若干个数,使它们的和恰好等于target。
转移方程:
dp[j] = dp[j] || dp[j - num]
(即:如果不选 num,dp[j] 保持原值;如果选 num,只要 dp[j - num] 为真即可。)
最后返回 dp[target] 是否为真。

题解代码分析
下面是完整的 Swift 实现代码,并带详细注释:
import Foundationclass Solution {func canPartition(_ nums: [Int]) -> Bool {// 1. 计算数组总和let totalSum = nums.reduce(0, +)// 2. 如果总和是奇数,不可能分割成相等的两部分if totalSum % 2 != 0 { return false }let target = totalSum / 2let n = nums.count// 3. 创建一维 DP 数组,表示能否组成和为 jvar dp = Array(repeating: false, count: target + 1)dp[0] = true // 和为0总是可以实现(什么都不选)// 4. 遍历数组,每个数字只能用一次(0/1背包)for num in nums {// 注意要倒序遍历,防止重复使用同一个数for j in stride(from: target, through: num, by: -1) {dp[j] = dp[j] || dp[j - num]}}// 5. 结果:能否恰好拼出 targetreturn dp[target]}
}
这段代码的核心逻辑在于:
- 使用一维动态规划数组,节省空间;
- 倒序更新 dp,确保每个元素只使用一次;
- 时间复杂度为
O(n * target),在数据范围内表现很优。
示例测试及结果
我们写一个简单的测试 Demo 来验证代码运行效果:
let solution = Solution()print(solution.canPartition([1, 5, 11, 5])) // 输出 true
print(solution.canPartition([1, 2, 3, 5])) // 输出 false
print(solution.canPartition([2, 2, 3, 5])) // 输出 false
print(solution.canPartition([1, 2, 5])) // 输出 false
print(solution.canPartition([1, 1, 1, 2, 2])) // 输出 true
运行结果:
true
false
false
false
true
这些结果完全符合题意。
尤其是 [1,1,1,2,2] 这个例子,很容易出错:
它能分为 [1,1,2] 和 [1,2],两边都是 4,说明算法逻辑正确。
时间复杂度
动态规划部分的循环是 n * target,其中:
n是数组长度(最大 200)target是sum / 2,最大约为 10000
所以时间复杂度为:
O(n * target)
在本题范围内完全可行。
空间复杂度
我们只用了一个长度为 target + 1 的一维数组:
O(target)
如果使用二维 DP(传统写法),空间会是 O(n * target),但在这里我们做了优化,减少了空间占用。
总结
这道题的核心是将“集合分割”转化为“背包问题”:
- 用动态规划的方式判断能否拼出目标和;
- 从二维优化为一维后,代码更简洁、效率更高;
- 核心点在于:从后往前更新 dp 数组,防止重复使用同一个元素。
