力扣416:分割等和子集

力扣416:分割等和子集

题目

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

思路

我们先从最简单的思路开始想起,一个数组想要分成两个子集并且子集和相同那么这个数组的和必须是奇数并且数组的大小必须大于2。所以我们可以先对这两个进行判断,其次在求数组和时我们还可以求一下数组的最大元素,然后我们把数组和除2也就得到了两个子集的和target,我们再用子集和和数组的最大元素进行比较,如果最大元素已经大于子集和了也就可以返回false了因为最大元素已经大于子集和了其他的所有元素加起来都不可能等于子集和了。

那么在对特殊情况进行判断也就是剪枝之后我们就可以判断正常情况了,在去掉前面的情况后现在的题目就变成了在数组中是否有一个子序列的序列和为target。在进行问题转换后我们可以来思考一个问题,我们是不是可以把这个问题拆分开来也就是形成一个一个的子问题,那么子问题是什么呢?我们设置i为数组的下标,j为目标值,子问题就是数组的前i个数字是否有一种选法让和为j。那么这个i的范围就是0,nums.size()),j的范围就是\[0,target,所以我们可以定义一个二维数组dp,dpij就是数组nums的前i个元素是否有一种选择方式可以让和为j,如果有就设为true没有就设为false,有子问题有dp数组这不就是动态规划吗。

在定义了dp数组后我们先来判断边界问题也就是i和j分别为0的情况,当i为0的时说明我们只有一个数字可以选择所以dp0nums\[0] = true,当j为0时目标值为0所以无论i为多少我们肯定能得到目标值所以dpi0 = true。

在解决了边界问题后我们就要解决普遍情况了,也就是推导状态转移方程即dpij = ?,我们有两个值可以拿来用一个是当前值numsi一个是目标值j,这两个值之间有什么关系吗。当然是有的,如果当前值大于目标值说明这个值不能被选,如果当前值小于目标值说明这个值可以被选也可以不被选,总的来说一个值只有选和被选两种情况。

  1. 被选
    当一个值要被选择时,我们想要判断选上这个数字numsi后是否等于j就需要考虑上一个数字numsi-1以及目标值j-numsi。因为我们选上这个数后前面的子序列的和必须是j-numsi
    所以一个数被选择后
    d p i j = d p i − 1 j − n u m s \[ i ] dpij = dpi-1j-nums\[i] dpij=dpi−1j−nums\[i]
  2. 不被选
    当一个值不被选时我们就只需要考虑前一个数字和当前目标值的是否满足条件了
    d p i j = d p i − 1 j dpij = dpi-1j dpij=dpi−1j

所以总的状态转移方程是
d p i j = { d p i − 1 j − n u m s \[ i ] ∣ d p i − 1 j ( j > = n u m s i ) d p i − 1 j ( j < n u m s i ) } dpij = \begin{Bmatrix}dpi-1j-nums\[i]| dpi-1j (j >= numsi) \\dpi-1j(j < numsi)\end{Bmatrix} dpij={dpi−1j−nums\[i]∣dpi−1j(j>=numsi)dpi−1j(j<numsi)}

代码

cpp 复制代码
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n = nums.size();
        if (n < 2) {
            return false;
        }
        int total = 0;
        int maxnumber = INT_MIN;
        for (int i = 0; i < n; i++) {
            total += nums[i];
            maxnumber = max(maxnumber, nums[i]);
        }
        // 数组和为奇数则不可能分为两个子集
        if (total % 2 != 0) {
            return false;
        }
        int target = total / 2;
        // 如果最大值小于数组和的一半就说明其他的数加起来也不可能等于最大的数
        if (maxnumber > target) {
            return false;
        }

        // n行,target+1列
        // 行代表是数组的前几个数
        // 列代表的是目标值是多少
        // dp[i][j]里存储的就是这前几个数有没有可能和为目标值
        vector<vector<int>> dp(n, vector<int>(target + 1, 0));

        // 列为0时目标值为0,当然有可能
        for (int i = 0; i < n; i++) {
            dp[i][0] = true;
        }

        // 行为0时只有一个数可以被选,对应的位置为true
        dp[0][nums[0]] = true;

        // 非特殊位置
        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= target; j++) {
                // 判断j和num[i]的大小关系
                if (j >= nums[i]) {
                    // 如果j大于等于nums[i]就说明这个数字是可以被选也可以不被选的
                    // 被选时dp[i][j]的值就取决于dp[i-1][j-nums[i]]
                    // 不被选dp[i][j]的值取决于dp[i-1][j]
                    dp[i][j] = dp[i - 1][j - nums[i]] | dp[i - 1][j];
                } else {
                    // j小于nums[i]说明这个值不能被选
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[n - 1][target];
    }
};
相关推荐
vibecoding日记10 小时前
双非如何快速入职字节等大厂大模型?真实案例分析:推理优化和投机解码
算法·求职·大模型工程师
yszaygr213812 小时前
Verilog参数化游程编码RLE模块
算法
望易13 小时前
刚设计的大模型架构-双域耦合认知框架
算法·架构
复杂网络16 小时前
多个 Claude Code 与多个 Codex 协同工作:设计与实现方案
算法
apocelipes1 天前
常用编程语言和库的正则表达式性能对比
c语言·c++·python·性能优化·golang·开发工具和环境
HjhIron1 天前
面试常客:字符串算法从入门到进阶
算法·面试
吴佳浩1 天前
DeepSeek DSpark:Confidence-Scheduled Speculative Decoding 技术解析
人工智能·算法·deepseek
触底反弹1 天前
🧠 搞懂 Token,才算真正入门大模型——从分词原理到 Embedding 语义实战
javascript·人工智能·算法
vivo互联网技术2 天前
ICLR 2026 | 基于后验采样的图像恢复方法LearnIR:人脸去阴影、去雾
人工智能·算法·aigc