89.分割等和子集

3 阅读1分钟

题目链接

给你一个 只包含正整数 的 非空 数组 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)