59 分割等和子集

分割等和子集

    • [NP 完全问题(01背包)](#NP 完全问题(01背包))
    • [题解1 二维DP](#题解1 二维DP)
    • [题解2 空间优化DP(改为1D)](#题解2 空间优化DP(改为1D))

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

示例 1:

输入:nums = [1,5,11,5]

输出:true

解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

输入:nums = [1,2,3,5]

输出:false

解释:数组不能分割成两个元素和相等的子集。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

NP 完全问题(01背包)

特点是:「每个数只能用一次」。解决的基本思路是:物品一个一个选,容量也一点一点增加去考虑,这一点是「动态规划」的思想
对于「0-1 背包问题」而言就是「当前考虑到的数字选与不选」:

  1. 不选择 nums[i],如果在 [0, i - 1] 这个子区间内已经有一部分元素,使得它们的和为 j ,那么 dp[i][j] = true;
  2. 选择 nums[i],如果在 [0, i - 1] 这个子区间内就得找到一部分元素,使得它们的和为 j - nums[i]。
  3. 状态转移方程为 dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]

注意:观察状态转移方程,or 的结果只要为真,表格 这一列 下面所有的值都为真。

因此在填表的时候,只要表格的最后一列是 true,代码就可以结束,直接返回 true。(题解2)

题解1 二维DP

cpp 复制代码
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int s = nums.size();
        int sumN = 0;
        
        for(auto&k : nums) sumN += k;
        // 总和不是偶数就不能分割成2 parts了
        if(sumN % 2) return false;
        
        sumN = sumN >> 1;
        // dp[i][j]: 从[0,i]范围内选取若干个正整数(可以是0个),是否存在一种选取方案使得被选取的正整数的和(物品重量总和)等于j(背包容量)
        // 如果dp.row = s,则需要设定dp[0][nums[0]]=true;后面的代码逻辑可以保持一致(有i-1)
        vector<vector<bool>> dp(s+1, vector<bool>(sumN+1, false));
        // 背包容量为0时 不放入就满足条件    
        for(int i = 1; i < s; i++){
            dp[i][0] = true;
        }

        for(int i = 1; i <= s; i++){
            for(int j = 1; j <= sumN; j++){
                if(j < nums[i-1])
                // 背包容量不足,不能装
                    dp[i][j] = dp[i-1][j];
                else
                // 这样可以把状态传递到最后的元素
                // 要么装,要么不装(和上一个情况保持一致)
                    dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
            }
        }       
        return dp[s][sumN];
    }
};

题解2 空间优化DP(改为1D)

cpp 复制代码
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int s = nums.size();
        int sumN = 0, maxN = 0;
        
        for(auto&k : nums){
            sumN += k;
            maxN = max(maxN, k);
        }
        if(sumN % 2) return false;
        
        sumN = sumN >> 1;
        // 剪枝
        if(maxN > sumN) return false;

        vector<bool> dp(sumN+1, false);
        dp[0] = true;

        for(int i = 0; i < s; i++){
        // 这里注意从大到小计算(保证是前行的意思)
            for(int j = sumN; j >= nums[i]; j--){
            // 启发自dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];(只与上一行的值有关)
                dp[j] = dp[j] || dp[j-nums[i]];
            }
            // (可能会加速)
            if(dp[sumN]) return true;
        }
        return dp[sumN];
    }
};
相关推荐
云里雾里!7 分钟前
LeetCode 744. 寻找比目标字母大的最小字母 | 从低效到最优的二分解法优化
算法·leetcode
yuanmenghao8 分钟前
CAN系列 — (8) 为什么 Radar Object List 不适合“直接走 CAN 信号”
网络·数据结构·单片机·嵌入式硬件·自动驾驶·信息与通信
一条大祥脚21 分钟前
26.1.3 快速幂+容斥 树上dp+快速幂 带前缀和的快速幂 正序转倒序 子序列自动机 线段树维护滑窗
数据结构·算法
2301_7806698624 分钟前
List(特有方法、遍历方式、ArrayList底层原理、LinkedList底层原理,二者区别)
java·数据结构·后端·list
二狗哈26 分钟前
czsc入门5: Tick RawBar(原始k线) NewBar (新K线)
算法·czsc
Tisfy31 分钟前
LeetCode 0865.具有所有最深节点的最小子树:深度优先搜索(一次DFS + Python5行)
算法·leetcode·深度优先·dfs·题解
王老师青少年编程33 分钟前
信奥赛C++提高组csp-s之二分图
数据结构·c++·二分图·csp·信奥赛·csp-s·提高组
Q741_14735 分钟前
C++ 队列 宽度优先搜索 BFS 力扣 429. N 叉树的层序遍历 C++ 每日一题
c++·算法·leetcode·bfs·宽度优先
Yzzz-F36 分钟前
P4145 上帝造题的七分钟 2 / 花神游历各国[线段树 区间开方(剪枝) + 区间求和]
算法·机器学习·剪枝
Zzz不能停39 分钟前
堆排序算法及大小堆区别
数据结构·算法