LeetCode416. 分割等和子集

这篇具有很好参考价值的文章主要介绍了LeetCode416. 分割等和子集。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

二、题解

方法一:0-1背包二维数组

步骤1:理解题目以及01 背包问题

在 01 背包问题中,我们有一系列物品,每个物品有自己的重量和价值,目标是选择一些物品放入背包中,使得背包的总重量不超过一定限制,同时所选物品的总价值最大。

题目要求将数组分割成两个子集,使得两个子集的元素和相等。这实际上就是在数组中寻找一个子集,使得这个子集的元素和等于整个数组元素和的一半。如果能找到这样的子集,那么剩余的元素的和也必然等于整个数组元素和的一半。

步骤2:将问题转化为 01 背包问题

将这道题目与 01 背包问题联系起来,可以将数组元素看作是物品的重量,而数组元素的值看作是物品的价值。我们的目标是将这些物品放入“背包”中,使得背包的总重量等于数组元素和的一半,同时价值最大。如果我们能找到一种放置物品的方式,使得背包中的物品总价值等于数组元素和的一半,那么剩余的物品总价值也必然等于数组元素和的一半,从而满足题目要求。

步骤3:套用0-1背包问题动态规划思路

在 01 背包问题中,动态规划的状态转移方程通常是:

dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])

其中,dp[i][j] 表示在前 i 个物品中,背包的容量为 j 时的最大价值,weight[i] 表示第 i 个物品的重量,value[i] 表示第 i 个物品的价值。

在本题中,dp[i][j]

  • i: 表示考虑前 i 个元素(或前 i 个物品,类比于 01 背包问题中的物品)。
  • j: 表示目标数值,即我们希望在考虑前 i 个元素的情况下,能够凑出的元素和(或背包的容量,类比于 01 背包问题中的背包容量)。

因此,dp[i][j] 的含义是:在考虑前 i 个元素的情况下,能否凑出元素和等于 j

具体来说,dp[i][j] 可以有两种可能的取值:

  1. 如果可以在考虑前 i-1 个元素的情况下,凑出元素和等于 j,那么我们不需要使用第 i 个元素,所以 dp[i][j] = dp[i-1][j]
  2. 如果可以在考虑前 i-1 个元素的情况下,凑出元素和等于 j-nums[i],那么我们可以使用第 i 个元素,所以 dp[i][j] = dp[i-1][j-nums[i]]

步骤4:初始化和边界条件

vector<vector<int>> dp(nums.size(),vector<int>(sum/2+1,0));
for(int i = 0; i < sum/2 + 1; i++){
    if(nums[0] <= i){
        dp[0][i] = nums[0];
    } 
}

dp 数组的第一行中的值初始化为 0,然后对于第一行中的某个位置 dp[0][i],如果 nums[0] <= i,说明第一个元素可以放入背包中凑出和为 i,所以将其值设为 nums[0];第一列因为j本身代表元素和,因此j = 0时nums[0][j] = 0.

步骤5:填充动态规划表

我们使用两个循环来遍历动态规划数组 dp 的每个位置。其中 i 表示考虑前 i 个元素,j 表示目标数值。

  • i 循环:我们从 i = 1 开始遍历,因为 i = 0 的情况已经在初始化部分处理过了,我们从第二个元素开始考虑。
  • j 循环:我们从 j = 1 开始遍历,因为不需要考虑元素和为 0 的情况,从小到大逐步考虑不同的目标数值。

在每个位置 (i, j),我们需要根据当前的元素值 nums[i] 以及目标数值 j,判断是否可以在前 i 个元素中凑出元素和为 j。我们有两种选择:

  1. 如果当前元素值 nums[i] 大于目标数值 j,那么无法将当前元素放入凑出目标数值,因此 dp[i][j] = dp[i-1][j],即继承前一个状态的值。
  2. 如果当前元素值 nums[i]小于等于目标数值 j,我们有两种选择:
    • 选择不将当前元素放入,那么 dp[i][j] = dp[i-1][j]
    • 选择将当前元素放入,我们需要在前 i-1 个元素中寻找一种组合,使得元素和为 j-nums[i],并且将当前元素值 nums[i] 加上。因此,dp[i][j] = max(dp[i-1][j-nums[i]] + nums[i], dp[i-1][j]),即选择两种选择中的较大值。

通过这两种选择,我们可以逐步填充 dp 数组,最终得到在不同元素和的情况下,是否存在一种组合方式,使得元素和接近或等于目标数值。这就是动态规划状态转移的过程,通过不断更新 dp 数组中的值,我们可以在最后的状态中找到问题的解。

for(int i = 1; i < nums.size(); i++){
    for(int j = 1; j < sum/2+1; j++){
        if(j < nums[i]){
            dp[i][j] = dp[i-1][j];
        } else {
            dp[i][j] = max(dp[i-1][j-nums[i]] + nums[i], dp[i-1][j]);
        }
    }
}

步骤6:寻找答案

在填充完整个 dp 数组后,我们需要寻找一个子集,使得其元素和等于整个数组元素和的一半。我们可以在 dp[i][sum/2] 处找到这个值,如果等于 sum/2,则表示存在这样的子集,返回 true,否则返回 false

 for(int i = 0; i < nums.size(); i++){
    if(dp[i][sum/2] == sum/2){
        return true;
    }
 }
 return false;

总代码(和上面有一点点不同,进行了一定的变量改进)

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(int i = 0; i < nums.size(); i++){
            sum += nums[i];
        }
        // 如果数组元素和为奇数,无法分割成两个元素和相等的子集
        if(sum % 2 == 1){
            return false;
        }
        
        int target = sum / 2; // 目标数值为元素和的一半
        vector<vector<int>> dp(nums.size(),vector<int>(target+1,0));
        
        // 初始化第一行,表示只考虑第一个元素时的情况
        for(int i = 0; i < target + 1; i++){
            if(nums[0] <= i){
                dp[0][i] = nums[0];
            } 
        }
        
        // 填充动态规划数组
        for(int i = 1; i < nums.size(); i++){
            for(int j = 1; j < target + 1; j++){
                if(j < nums[i]){
                    // 当前元素值大于目标数值,无法放入背包
                    dp[i][j] = dp[i-1][j];
                }else{
                    // 选择放入当前元素或不放入,取较大值
                    dp[i][j] = max(dp[i-1][j-nums[i]]+nums[i], dp[i-1][j]);
                }
            }
        }
        
        // 检查是否有一种组合方式使得元素和等于目标数值
        for(int i = 0; i < nums.size(); i++){
            if(dp[i][target] == target){
                return true;
            }
        }
        
        return false;
    }
};
方法二:0-1背包一维数组

思路和上面大差不差,但是可以节约空间。文章来源地址https://www.toymoban.com/news/detail-684229.html

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(int i = 0; i < nums.size(); i++){
            sum += nums[i];
        }
        // 如果数组元素和为奇数,无法分割成两个元素和相等的子集
        if(sum % 2 == 1){
            return false;
        }
        int target = sum / 2; // 目标数值为元素和的一半
        vector<int> dp(target+1, 0); // 使用一维数组来保存状态
        
        // 填充动态规划数组
        for(int i = 0; i < nums.size(); i++){
            // 注意循环条件是 j >= nums[i],从后往前遍历
            for(int j = target; j >= nums[i]; j--){
                // 选择放入当前元素或不放入,取较大值
                dp[j] = max(dp[j-nums[i]] + nums[i], dp[j]);
            }
        }
        
        // 检查是否有一种组合方式使得元素和等于目标数值
        if(dp[target] == target){
            return true;
        }
        
        return false;
    }
};

到了这里,关于LeetCode416. 分割等和子集的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 01背包问题-递推公式的自我理解与LeetCode 416. 分割等和子集

    学算法好痛苦,完全是对我智力的一次次折磨,看了好多博客,对二维dp数组的理解都是直接搬了代码随想录,搬了随想录又没详细解释,大家都是一眼看懂的吗,好吧() 如果有一个容量为 j 的这样的背包——一个独立的,容量为j的背包。(没把它理解为“剩余容量”) 对于

    2024年02月07日
    浏览(45)
  • [Leetcode] 416. 分割等和子集、1049. 最后一块石头的重量 II、494. 目标和、474. 一和零

    内容:今天复习下dp数组中的背包问题 分割等和子集 - 能否装满 最后一块石头 - 尽可能装满 目标和 - 有多少种方法装 一和零 - 装满背包有多少个物品 416. 分割等和子集 10背包:用/不用;有容量;有价值 dp[j] : 容量为j,最大价值为dp[j]         重量和价值等价 dp[target] == t

    2024年02月16日
    浏览(43)
  • 算法训练第四十二天|01背包问题 二维 、01背包问题 一维、416. 分割等和子集

    视频链接:https://www.bilibili.com/video/BV1cg411g7Y6/ 参考:https://programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%E8%83%8C%E5%8C%85-1.html 对于面试的话,其实掌握01背包,和完全背包,就够用了,最多可以再来一个多重背包。 而完全背包又是也是01背包稍作变化而来,即:完全

    2024年02月01日
    浏览(46)
  • 代码随想录 Day35 动态规划04 01背包问题和完全背包问题 LeetCode T416 分割等和子集

    说到背包问题大家都会想到使用动规的方式来求解,那么为什么用动规呢, dp数组代表什么呢 ? 初始化是什么 , 遍历方式又是什么 ,这篇文章笔者将详细讲解背包问题的经典例题0-1背包问题和完全背包问题的解题方式,希望能帮助到大家 有人一提到背包问题就只会使用动态规划来

    2024年02月06日
    浏览(77)
  • 【力扣】416. 分割等和子集 <动态规划、回溯>

    给你一个 只包含正整数的非空数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 示例 1: 输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。 示例 2: 输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和

    2024年02月10日
    浏览(40)
  • 代码随想录day42|背包问题、416. 分割等和子集

     背包问题:    01 背包 二维数组dp[i][j]解法 纯01背包:有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。 每件物品只能用一次 ,求解将哪些物品装入背包里物品价值总和最大。 dp[i][j]:从下标为[0-i]的物品里任意取,放进容量为j的

    2024年04月09日
    浏览(62)
  • 力扣hot100:416.分割等和子集(组合/动态规划/STL问题)

    组合数问题 我们思考一下,如果要把数组分割成两个子集,并且两个子集的元素和相等,是否等价于在数组中寻找若干个数使之和等于所有数的一半?是的! 因此我们可以想到,两种方式: ①回溯的方式找到target,但是回溯是阶乘级别的算法,这里会超时。 ②从前往后遍历

    2024年04月28日
    浏览(38)
  • 力扣算法刷题Day42|动态规划:01背包问题 分割等和子集

    力扣题目:01背包问题(二维数组) 刷题时长:参考题解 解题方法:动态规划 + 二维dp数组 复杂度分析 时间 空间 问题总结 理解递推公式困难 本题收获 动规思路:两层for循环,第一层i遍历物品,第二层j枚举背包容量以内所有值 确定dp数组及下标的含义:dp[i][j] 表示从下标

    2024年02月13日
    浏览(62)
  • 【Day42】代码随想录之动态规划0-1背包_416. 分割等和子集

    动态规划理论基础 动规五部曲: 确定dp数组 下标及dp[i] 的含义。 递推公式:比如斐波那契数列 dp[i] = dp[i-1] + dp[i-2]。 初始化dp数组。 确定遍历顺序:从前到后or其他。 推导dp数组。 出现结果不正确: 打印dp日志和自己想的一样:递推公式、初始化或者遍历顺序出错。 打印

    2024年02月20日
    浏览(68)
  • 【十七】【动态规划】DP41 【模板】01背包、416. 分割等和子集、494. 目标和,三道题目深度解析

    动态规划就像是解决问题的一种策略,它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题,并将每个小问题的解保存起来。这样,当我们需要解决原始问题的时候,我们就可以直接利用已经计算好的小问题的解,而不需要重

    2024年02月03日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包