题目
题目来源:力扣(LeetCode)
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
思路分析
可达数组: 在某种情况下,可以达到某个值
1、计算元素组的和值
2、判断原数组的数字是否可以凑出和值的一半,可以凑出来证明可以被分割
3、状态定义:f[i][j],前i个数字是是否能凑出j值。分成两种情况
4、动态转移方程:f[i][j] = f[i-1][j] / f[i-1][j-num[i]];
f[i-1][j]:没有使用第i个数字
f[i-1][j-num[i]]:使用第i个数字 ,剩余数字等于前i-1个数字,拼凑j-num[i] 这两个状态,只要有一个状态是1, f[i][j] = 1,前i个数字可以拼凑出来j,前i个数字到j是可达的;
var canPartition = function (nums) {
let sum = 0;
for (const x of nums) sum += x;
// 特殊判断,如果数组元素的和值是一个奇数,直接返回false
if (sum % 2) return false;
const dp = new Array(sum + 1);
// 一开始所有的状态都是0,都是不可达
for (let i = 1; i <= sum; i++) dp[i] = 0;
dp[0] = 1;
sum = 0;//当前值之前所有值能够拼凑出来的最大值
for (const x of nums) {
sum += x;
// 倒着扫描
for (let j = sum; j >= x; j--) {
dp[j] |= dp[j - x];
}
}
return dp[sum / 2];
};
二维数组解法
/**
* @param {number[]} nums
* @return {boolean}
*/
var canPartition = function (nums) {
const n = nums.length;
// n < 2,不可能将数组分割成元素和相等的两个子集,因此返回 false
if (n < 2) {
return false;
}
// 计算整个数组的元素和 sum 以及最大元素 maxNum
let sum = 0, maxNum = 0;
for (const num of nums) {
sum += num;
maxNum = maxNum > num ? maxNum : num;
}
// 如果 sum 是奇数,则不可能将数组分割成元素和相等的两个子集,因此返回 false
if (sum & 1) {
return false;
}
// 「等和子集」的和必然是总和的一半
const target = Math.floor(sum / 2);
// 如果 maxNum > target,
// 则除了 maxNum 以外的所有元素之和一定小于 target,
// 因此不可能将数组分割成元素和相等的两个子集,直接返回 false
if (maxNum > target) {
return false;
}
// 创建二维数组 dp,包含 n 行 target + 1 列
// dp[i][j] 表示从数组的 [0, i] 下标范围内选取若干个正整数(可以是 0 个),
// 是否存在一种选取方案使得被选取的正整数的和等于 j
// 初始时,dp 中的全部元素都是 false
const dp = new Array(n).fill(0).map(v => new Array(target + 1, false));
// 两种边界情况
// 1、如果不选取任何正整数,则被选取的正整数等于 0。因此对于所有 0 ≤ i < n,都有 dp[i][0]=true
for (let i = 0; i < n; i++) {
dp[i][0] = true;
}
// 2、当 i == 0 时,只有一个正整数 nums[0] 可以被选取,因此 dp[0][nums[0]]=true
dp[0][nums[0]] = true;
// 先遍历物品(数值),再遍历背包(元素和)
for (let i = 1; i < n; i++) {
const num = nums[i];
for (let j = 1; j <= target; j++) {
// 如果 j > nums[i],对于当前的数字 nums[i],可以选取也可以不选取,
// 两种情况只要有一个为 true,就有 dp[i][j] 为true
if (j >= num) {
dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
} else {
// 如果 j < nums[i],则在选取的数字的和等于 j 的情况下无法选取当前的数字 nums[i],
// 因此有 dp[i][j]=dp[i−1][j]
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n - 1][target];
};
参考阅读: https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/fen-ge-deng-he-zi-ji-by-leetcode-solution/ https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/bang-ni-ba-0-1bei-bao-xue-ge-tong-tou-by-px33/ https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/