01背包:分割等和子集

75 阅读1分钟

leetcode: 416. 分割等和子集

题目描述

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例

输入: nums = [1,5,11,5]
输出: true
解释: 数组可以分割成 [1, 5, 5][11]

解题思路

问题要求判断数组能否分为两个总和相同的子集,那么就可以转换为是否存在子集满足总和为数组总和的一半。

01背包即背包中的每件物品只能使用一次。

二维解法

容易想到,用dp[i][j]表示使用前i件物品,总价值不超过j的最大价值。

那么,dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v] + v)

  • 不使用当前物品
  • 使用当前物品(判断j值)

一维优化

dp[i][j]压缩到一维dp[j],更新dp[j] = min(dp[j], dp[j - v] + v)

  • 外循环仍然是前i件物品
  • 内循环j从大到小
    • 当前遇到dp[j]实际为dp[i - 1][j],即判断的是min(dp[i - 1][j], dp[i - 1][j - v] + v)
    • 如果从小到大 dp[j] (dp[i - 1][j])会被提前更新为dp[i][j],即判断的是min(dp[i - 1][j], dp[i][j - v] + v)

完整代码:

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (1 == sum % 2) return false;

        int target = sum / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        for (int i = 1; i <= nums.length; i++) {
            int val = nums[i - 1];
            for (int j = target; j >= val; j--) {
                if (!dp[j]) dp[j] = dp[j - val];
            }
            if (dp[target]) return true;
        }

        return false;
    }
}