给你一个 只包含正整数 的 非空 数组
nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
解法1 记忆化dfs
思路
两个子集的和相等,是不是说明这个数组的和是个偶数?那接下来是不是知道了每个子数组的和?就是数组之和的一半。
那我们接下来可以从 0
开始累加,利用背包的思想,拿或者不拿(加或者不加)当前 index
上的数,直到遍历完数组。
为了避免重复计算,用 index-currentSum
来表示之前计算过的结果。
代码
function canPartition(nums: number[]): boolean {
const sum = nums.reduce((a, c) => a + c, 0);
if (sum % 2 !== 0) return false;
const target = sum / 2;
const memo = new Map<string, boolean>();
const canFindSubset = (index, currentSum) => {
if (currentSum === target) return true;
if (currentSum > target || index >= nums.length) return false;
const key = `${index}-${currentSum}`;
if (memo.has(key)) return memo.get(key);
const result = canFindSubset(index + 1, currentSum + nums[index]) || canFindSubset(index + 1, currentSum);
memo.set(key, result);
return result;
};
return canFindSubset(0, 0);
};
时空复杂度
时间复杂度:O(n * target)
空间复杂度:O(n * target)
解法2 动态规划
思路
定义状态:初始化 dp[0] = true
,表示总能用空集组成和为0。
每个数字只能使用一次(0/1背包的特点),所以我们从右向左更新 dp[j]
。
每次 dp[j] = true
的含义是我们可以从数组中某些数凑出和为 j
。
代码
function canPartition(nums: number[]): boolean {
const sum = nums.reduce((a, c) => a + c, 0);
if (sum % 2 !== 0) return false;
const target = sum / 2;
const dp = new Array(target + 1).fill(false);
dp[0] = true;
for (const num of nums) {
for (let j = target; j >= num; j--) {
dp[j] = dp[j] || dp[j - num];
}
}
return dp[target];
};
时空复杂度
时间复杂度:O(n * target)
空间复杂度:O(target)