背包:数组是否可以分割两个子集,使得这两子集的元素和相等

目录

题目

力扣(LeetCode)

image.png|656

题解

这道题是 14. 背包:0-1 背包问题 的变种:

  • 给一个可装载重量为 sum / 2 的背包和 N 个物品,每个物品的重量为nums[i]。现在让你装物品,是否存在一种装法,能够恰好将背包装满。

明确 状态选择

状态:

  • 背包还能装多少重量的物品
  • 还能选择的物品 选择:
  • 进背包
  • 不装进背包

明确 dp数组 的定义

定义

dp[i][w] = x 表示:

  • 对于前 i 个物品( i 从 1 开始计数),当前背包的容量为 w 时,是否能装满背包
    • xtrue,则说明 可以恰好将背包装满
    • xfalse,则说明不能恰好将背包装满

所以,本题的题解就是求:dp[N][sum/2]

base case

  • dp[...][0] = true ,表示没有容量,装满了
  • dp[0][...]= false ,表示没有可选择的物品,不能装满

根据 选择 确定 状态转移方程

1. 选择一:不选

dp[i][w] = dp[i-1][w]

解释:

你不把第i 装入包中时,是否装满只取决于 前 i-1 个物品 是否装满剩余容量为 w 的背包

2. 选择二:选择

dp[i][w] = dp[i-1][w-num[i-1]]

解释:

你把第i 装入包中时,是否装满只取决于 前 i-1 个物品 是否装满剩余容量为 w - num[i-1] 的背包

换句话说,如果 w - nums[i-1] 的 重量可以被恰好装满,那么只要把第 i 个物品能装进去,也可恰好装满 w 的重量

根据 动态规划框架 写出 最终代码

/**
 * @description 分割等和子集, 0/1背包的变种
 * @url https://leetcode-cn.com/problems/partition-equal-subset-sum/
 * @param {number} N
 * @param {number} W
 * @param {number[]} wt
 * */
function fn(N, W, wt) {
    // :::: 初始化dp数组 及其 base case
    const dp = [];
    for (let i = 0; i <= N; i++) {
        dp[i] = [];
        for (let w = 0; w <= W; w++) {
            // ::::重量为0时,表示能够装入
            if (w === 0) {
                dp[i][w] = true;
            }
            // ::::可选择的物品为0时,表示不能装入
            else if (i === 0) {
                dp[i][w] = false;
            }
            // ::::其余情况,初始化为false
            else {
                dp[i][w] = false;
            }
        }
    }
    // ::::根据状态个数,决定嵌套层数,
    // ::::使用动态规划框架模板代码遍历
    for (let i = 1; i <= N; i++) {
        for (let w = 1; w <= W; w++) {
            // ::::剩余容量已经小于 0 了,只能取决于上一个物品的状态,是否装满了
            if (w - wt[i - 1] < 0) {
                dp[i][w] = dp[i - 1][w];
            } else {
                // ::::选择
                dp[i][w] =
                    // ::::不装入
                    dp[i - 1][w] ||
                    // ::::装入
                    dp[i - 1][w - wt[i - 1]];
            }
        }
    }
    return dp[N][W];
}


/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canPartition = function (nums) {
    const sum = nums.reduce((a, b) => a + b, 0);
    // ::::如果总和为奇数,直接返回false
    if (sum % 2 !== 0) return false;
    // ::::背包容量
    const W = sum / 2;
    // ::::物品数量
    const N = nums.length;
    return fn(N, W, nums);
};

优化空间复杂度

var canPartition = function (nums) {
    let W = 0;
    for (let num of nums) W += num;
    // 和为奇数时,不可能划分成两个和相等的集合
    if (W % 2 !== 0) return false;
    let N = nums.length;
    W = W / 2;

    let dp = new Array(W + 1).fill(false);

    // base case
    dp[0] = true;

    for (let i = 0; i < N; i++) {
        // ::::: 从后往前遍历
        for (let w = W; w >= 0; w--) {
            if (w - nums[i] >= 0) {
                dp[w] = dp[w] || dp[w - nums[i]];
            }
        }
    }
    return dp[W];
};

时间复杂度 O(n*sum),空间复杂度 O(sum)

  • 注意是倒序,记住正常的解法就行,这种压缩空间复杂度的解法,还没理解透