LeetCode 416:分割等和子集 ------ (0-1背包)
🔗 题目链接
👉 https://leetcode.cn/problems/partition-equal-subset-sum/
📖 内容概要
给定一个只包含正整数的非空数组 nums,判断是否可以将它分成两个子集,使得两个子集的元素和相等。
✅ 使用 0/1 背包思想
✅ 一维 DP 优化
✅ 时间复杂度 O(n × sum)
✅ 空间复杂度 O(sum)
💡 解题思路
一、问题转化(最关键一步)
能否从数组中选出若干个数,使其和等于
sum / 2
如果能,那么剩下的数之和也一定是 sum / 2。
二、可行性剪枝
-
如果数组总和是奇数:
javaif (sum % 2 == 1) return false; -
不可能平分
三、0/1 背包建模
| 概念 | 对应 |
|---|---|
| 物品 | 数组中的每个数 |
| 重量 | 数值大小nums[i] |
| 价值 | 数值大小 nums[i] |
| 背包容量 | target = sum / 2 |
✅ 判断是否能恰好装满容量为 target 的背包
四、DP 定义
java
dp[j] = 容量为 j 的背包,能装的最大价值
目标:
java
dp[target] == target
五、状态转移方程
java
dp[j] = max(
dp[j], // 不选当前数
dp[j - nums[i]] + nums[i] // 选当前数
)
六、遍历顺序(非常重要)
java
for (int i = 0; i < n; i++) {
for (int j = target; j >= nums[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
✅ j 必须从大到小
- 防止同一物品被重复使用
- 保证是 0/1 背包
✅ AC 代码(Java)
java
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
// 奇数不可能平分
if (sum % 2 == 1) return false;
int target = sum / 2;
int[] dp = new int[20001]; // dp[j]: 容量为 j 的最大价值和
// 0/1 背包
for (int i = 0; i < nums.length; i++) {
for (int j = target; j >= nums[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}
⏱️ 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间复杂度 | O(n × sum) |
| 空间复杂度 | O(sum) |
⚠️ 时间复杂度不是
O(n²),而是由数值总和决定
✅ 核心总结一句话
把"能否平分数组"转化为"是否存在子集和为 sum/2",再用 0/1 背包判断是否恰好装满。