力扣第416题 *** 分割等和子集 c++ 新题 动态规划 中的 01背包问题

题目

416. 分割等和子集

中等

相关标签

数组 动态规划

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

思路一二都是基于动态规划来解释的。

不会的可以先去了解了解动态规划是什么,怎么做,模板为什么,等等

然后去了解纯01背包的问题。可以上B站搜,或者本站csdn搜索

这里提供B站视频

代码随想录 动态规划 理论基础https://www.bilibili.com/video/BV13Q4y197Wg/?spm_id_from=333.337.search-card.all.click&vd_source=fa18c7bb110345953d1f71c2974a5da6代码随想录 01背包 解题方法https://www.bilibili.com/video/BV1cg411g7Y6/?spm_id_from=333.788&vd_source=fa18c7bb110345953d1f71c2974a5da6代码随想录 01背包 进阶解法 https://www.bilibili.com/video/BV1BU4y177kY/?spm_id_from=333.788&vd_source=fa18c7bb110345953d1f71c2974a5da6

思路和解题方法 一 (二维数组dp法)

  1. 首先,计算数组 nums 的元素和,并判断是否为奇数,如果是,则无法平分成两个子集,直接返回 false

  2. 计算目标和 target,即每个子集的和应该为 sum / 2,同时判断数组 nums 中的最大值是否大于 target,如果是,则无法平分,直接返回 false

  3. 初始化一个大小为 n * (target+1) 的二维数组 dp,其中 dp[i][j] 表示在前 i 个元素中是否能够找到一些元素的和等于 j

  4. 将第一列 dp[i][0] 全部初始化为 true,因为任何元素都可以不选,使其和为 0。

  5. 将第一行 dp[0][nums[0]] 初始化为 true,因为只有第一个元素可以被选中,使其和为 nums[0]

  6. 从第二个元素开始遍历数组 nums,对于每个元素 nums[i],从 1 到目标和 target 进行遍历,计算 dp[i][j] 的值。

  7. 如果当前元素的值 nums[i] 大于当前的和 j,则不能将该元素加入到和为 j 的子集中,因此 dp[i][j] 的值与 dp[i-1][j] 相等。

  8. 如果当前元素的值 nums[i] 小于等于当前的和 j,则可以将该元素加入到和为 j 的子集中。此时,需要考虑两种情况:选中当前元素或不选中当前元素。因此,dp[i][j] 的值等于 dp[i-1][j]dp[i-1][j-nums[i]]

  9. 循环结束后,返回 dp[n-1][target] 的值,表示在前 n 个元素中是否能够找到一些元素的和等于目标和 target

复杂度

时间复杂度:

O(n*target)

时间复杂度:

  • 初始化动态规划数组:O(n * target),其中 n 是数组的长度,target 是目标和的大小。
  • 动态规划过程中的两层循环:O(n * target)。

因此,总体时间复杂度为 O(n * target)。

空间复杂度

O(n*target)

空间复杂度:

  • 动态规划数组:O(n * target),用于存储子问题的解。

因此,总体空间复杂度为 O(n * target)。

c++ 代码 一

cpp 复制代码
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n = nums.size();
        if (n < 2) {
            return false;
        }
        int sum = accumulate(nums.begin(), nums.end(), 0); // 计算数组元素的总和
        int maxNum = *max_element(nums.begin(), nums.end()); // 找到数组中的最大值
        if (sum & 1) { // 如果总和为奇数,则无法平分成两个子集
            return false;
        }
        int target = sum / 2; // 目标和为总和的一半
        if (maxNum > target) { // 如果最大值大于目标和,则无法平分
            return false;
        }
        vector<vector<int>> dp(n, vector<int>(target + 1, 0)); // 创建二维动态规划数组
        for (int i = 0; i < n; i++) {
            dp[i][0] = true; // 初始化第一列,表示任何元素都可以不选,使其和为0
        }
        dp[0][nums[0]] = true; // 初始化第一行,只有第一个元素可以被选中,使其和为nums[0]
        for (int i = 1; i < n; i++) { // 从第二个元素开始遍历数组
            int num = nums[i];
            for (int j = 1; j <= target; j++) { // 遍历目标和的范围
                if (j >= num) { // 如果当前和大于等于当前元素的值
                    dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num]; // 可选方案为选中当前元素或不选中当前元素
                } else { // 如果当前和小于当前元素的值
                    dp[i][j] = dp[i - 1][j]; // 只能选择不选中当前元素
                }
            }
        }
        return dp[n - 1][target]; // 返回最后一个元素对应的状态,表示是否能够找到一些元素的和等于目标和
    }
};

思路和解题方法 二(压缩二维 ☞ 一维)

  1. 首先,定义了一个类 Solution,其中包含了一个公有成员函数 canPartition,该函数接受一个整数数组 nums,并返回一个布尔值。

  2. 在函数内部,首先计算了数组 nums 中所有元素的和,并将结果存储在变量 sum 中。

  3. 接下来,通过判断 sum 是否为奇数,来确定是否可以平分数组。如果 sum 是奇数,则无法平分,直接返回 false

  4. 如果 sum 是偶数,说明可以进行平分。将 sum 除以 2 得到目标值 target,即每个子集的和应该为 target

  5. 初始化一个大小为 10001(为题目数据大小*长度 / 2) 的数组 dp,用于存储动态规划过程中的中间结果。数组 dp 的索引表示当前的和,数组的值表示是否可以组成该和。

  6. 接下来,使用两层循环遍历数组 nums 和目标值 target。外层循环遍历数组 nums,内层循环从 target 开始递减到当前元素的值 nums[i]

  7. 在内层循环中,更新数组 dp 的值。对于每个位置 j,比较 dp[j]dp[j-nums[i]] + nums[i] 的大小,取较大值作为 dp[j] 的新值。这表示在考虑当前元素 nums[i] 时,可以得到的最大和为 dp[j]

  8. 循环结束后,判断数组 dp 的最后一个元素 dp[target] 是否等于 target。如果是,则说明可以找到一个子集使其和为 target,返回 true;否则,返回 false

复杂度

时间复杂度:

O(n*target)

时间复杂度为 O(n×target),其中 n 是数组 nums 的长度,target 是数组 nums 中所有元素的和的一半。因为在内层循环中,需要遍历从 targetnums[i] 的所有可能的和,所以时间复杂度是 O(target),外层循环需要遍历整个数组 nums,所以时间复杂度是 O(n),因此总的时间复杂度是 O(n×target)。

空间复杂度

O(target)

这段代码的空间复杂度为 O(target),因为需要创建一个大小为 target+1 的数组 dp 来存储动态规划的中间结果。

c++ 代码 二

cpp 复制代码
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0); // 计算数组 nums 的所有元素的和
        if (sum % 2 == 1) { // 如果和为奇数,则无法平分成两个子集,直接返回 false
            return false;
        }
        int target = sum / 2; // 计算目标和,即每个子集的和应该为 sum / 2
        vector<bool> dp(target + 1, false); // 初始化一个大小为 target + 1 的布尔型数组 dp,表示能否组成目标和
        dp[0] = true; // 初始状态:目标和为 0 时肯定可以组成
        for (int i = 0; i < nums.size(); ++i) { // 遍历数组 nums
            for (int j = target; j >= nums[i]; --j) { // 从 target 开始递减到 nums[i]
                dp[j] = dp[j] || dp[j - nums[i]]; // 更新 dp[j] 的值
            }
        }
        return dp[target]; // 返回 dp[target] 的值,表示是否能够组成目标和
    }
};

觉得有用的话可以点点赞,支持一下。

如果愿意的话关注一下。会对你有更多的帮助。

每天都会不定时更新哦 >人< 。

相关推荐
九圣残炎22 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
lulu_gh_yu28 分钟前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
丫头,冲鸭!!!1 小时前
B树(B-Tree)和B+树(B+ Tree)
笔记·算法
Re.不晚1 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
ULTRA??1 小时前
C加加中的结构化绑定(解包,折叠展开)
开发语言·c++
凌云行者2 小时前
OpenGL入门005——使用Shader类管理着色器
c++·cmake·opengl
凌云行者2 小时前
OpenGL入门006——着色器在纹理混合中的应用
c++·cmake·opengl
为什么这亚子2 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
2 小时前
开源竞争-数据驱动成长-11/05-大专生的思考
人工智能·笔记·学习·算法·机器学习
~yY…s<#>2 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode