动态规划-01背包问题

本篇中将介绍几道01背包类型的问题。

分割等和子集

416. 分割等和子集 - 力扣(LeetCode)

题目描述

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

示例 1:

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

输出:true

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

示例 2:

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

输出:false

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

 我们可以将这个问题视为一个典型的“背包问题”的变种,具体来说是“0-1背包问题”的一个特殊应用。不过,在这个问题中,我们不是要求达到某个特定重量的最大价值,而是要求判断是否存在一种方式,将数组中的元素分为两组,使得两组的和相等。

问题分析

  1. 目标和:首先,我们需要计算数组 nums 中所有元素的总和 totalSum。如果 totalSum 是奇数,那么显然无法将数组分割成两个和相等的子集,因为两个相等的和加起来必须是偶数。

  2. 动态规划数组:如果 totalSum 是偶数,我们定义 target = totalSum / 2。接下来,我们使用动态规划来判断是否存在一个子集的和为 target。我们定义一个布尔型动态规划数组 dp,其中 dp[i] 表示是否存在一个子集,其和为 i。注意,这里的 i 的取值范围是 0 到 target(包含)。

  3. 状态转移方程

    • 初始化:dp[0] = true(空集的和为0)。
    • 对于数组 nums 中的每个元素 num,以及每个可能的和 j(从 target 开始向下遍历到 num),如果 dp[j-num] 为真(即存在一个子集的和为 j-num),则我们可以将 num 加到这个子集中,使得子集的和变为 j,因此设置 dp[j] = true
  4. 结果判断:如果 dp[target] 为真,则说明存在一个子集的和为 target,即可以将数组 nums 分割成两个和相等的子集。

 

bool canPartition(vector<int>& nums) {  
    int totalSum = 0;  
    for (int num : nums) {  
        totalSum += num;  
    }  
      
    // 如果总和是奇数,则无法分割成两个和相等的子集  
    if (totalSum % 2 != 0) {  
        return false;  
    }  
      
    int target = totalSum / 2;  
    vector<bool> dp(target + 1, false);  
    dp[0] = true; // 空集的和为0  
      
    for (int num : nums) {  
        for (int j = target; j >= num; --j) {  
            if (dp[j - num]) {  
                dp[j] = true;  
            }  
        }  
    }  
      
    return dp[target];  
}  

目标和

494. 目标和 - 力扣(LeetCode)

题目描述

给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 。返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

例如,nums = [3, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+3-1" 以及"+3+1"等表达式。 

解题思路

 将数组分成两份一部分加上正号,另一部分加上负号,加正号的那一部分总和为a,另一部分总和绝对值为b

题目要求即要使a - b = target;

并且a + b = sum;

所以题目要求就是找出数组中 和为 : (target+sum)/2 的所有组合数。实际上就是01背包问题。

我们可以定义一个二维数组 dp[i][j] 来表示在前 i 个数字中选取一些数字(每个数字前面可以添加 '+' 或 '-'),使得这些数字的和为 j 的不同表达式的数目

int findTargetSumWays(vector<int>& nums, int target) {
        int n = nums.size();
        
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += nums[i];
        }
        if ((target + sum) % 2 == 1)
            return false;
        int m = (target + sum) / 2;
        if (m < 0)
            return 0;
        vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
        // dp[i][j]: 前i个数 总和为j 的所有方法数目
        for (int i = 0; i <= n; i++) { // 前i个数 什么都不选总和为0
            dp[i][0] = 1;
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                if (j - nums[i - 1] >= 0) {
                    dp[i][j] = dp[i - 1][j - nums[i - 1]] + dp[i - 1][j];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[n][m];
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值