416.分割等和子集 | 518.零钱兑换II | 494.目标和

416.分割等和子集 

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

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。 

思路:动态规划(背包问题) 

经典动态规划:子集背包问题 :: labuladong的算法小抄 (gitee.io)

题意转换:给一个可装载重量为 sum / 2 的背包和 N 个物品,每个物品的重量为 nums[i]。现在让你装物品,是否存在一种装法,能够恰好将背包装满

状态:i(背包的容量)、j(可选择的物品)

选择:装进背包或不装进背包

dp数组定义:dp[i][j]表示从nums[0..i-1]中选取元素,能否将它们的元素和凑成j 

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        //定义:dp[i][j]表示从nums[0..i-1]中选取元素,能否将它们的元素和凑成j
        int n=nums.size();
        int sum=accumulate(nums.begin(),nums.end(),0);
        if(sum%2!=0)//和为奇数,不能分割成两个等和子集
            return false;
        sum/=2;
        vector<vector<bool>> dp(n+1,vector<bool>(sum+1,false));
        //这个base case可以省略,因为dp数组已经全部初始化为false了
        for(int j=1;j<=sum;j++)//i=0,nums[0..-1]中没有元素可选,所以凑不出j,都是false
            dp[0][j]=false;
        for(int i=0;i<=n;i++)//j=0,不需要再选元素就已经满足情况了,所以都是true
            dp[i][0]=true;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=sum;j++)
            {
                if(j-nums[i-1]<0)//背包容量不够,只能选择不装入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]];//选择num[i-1]装入或不装入背包
            }
        }
        return dp[n][sum];
    }
};

518.零钱兑换II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。 

题目数据保证结果符合 32 位带符号整数。

示例 1:

输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:

输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。

思路:完全背包问题(每一种面额的硬币有无限个)

经典动态规划:完全背包问题 :: labuladong的算法小抄 (gitee.io)

dp数组定义:dp[i][j]表示只用coins[0..i-1]中的面额凑出金额j的组合有多少种

状态转移:dp [i] [j] = dp [i-1] [j]  + dp [i] [ j-coins [ i-1 ] ];

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int n=coins.size();
        //定义:dp[i][j]表示只用coins[0..i-1]中的面额凑出金额j的组合有多少种
        vector<vector<int>> dp(n+1,vector<int>(amount+1,0));
        for(int i=0;i<=n;i++)//base case:凑出金额0的组合不论i为几,都只有一种(不操作)
            dp[i][0]=1;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=amount;j++)
            {
                if(j-coins[i-1]>=0)//状态转移
                {
                    //选择使用第i个硬币的面值 或 不使用第i个硬币的面值
                    //dp[i][j-coins[i-1]]依然允许使用第i个硬币的,所以说已经包含了重复使用硬币的情况
                    dp[i][j]=dp[i-1][j]+dp[i][j-coins[i-1]];
                }
                else
                {
                    //凑不出此金额,所以只能选择不使用第i个硬币的面值
                    dp[i][j]=dp[i-1][j];
                }       
            }
        }
        return dp[n][amount];
    }
};

494.目标和 

给你一个整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" .
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3


思路:动态规划(背包问题)

首先,如果我们把 nums 划分成两个子集 A 和 B,分别代表分配 + 的数和分配 - 的数,那么他们和 target 存在如下关系:

sum(A) - sum(B) = target
sum(A) = target + sum(B)
sum(A) + sum(A) = target + sum(B) + sum(A)
2 * sum(A) = target + sum(nums)

sum(A) = (target + sum(nums)) / 2,也就是把原问题转化成:nums 中存在几个子集 A,使得 A 中元素的和为 (target + sum(nums)) / 2 ?

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int n=nums.size();
        int sum=accumulate(nums.begin(),nums.end(),0);
        //如果所有元素的和比target的绝对值小,或者sum+target是奇数,则没有组合结果
        if(sum<abs(target)||(sum+target)%2!=0)
        {
            return 0;
        }
        //dp数组定义:dp[i][j]表示从nums[0..i-1]中选取元素,有几种方法可以使它们的和为j
        int m=(sum+target)/2;
        vector<vector<int>> dp(n+1,vector<int>(m+1,0));
        //base case(因为nums中的元素是整数,包含0,所以j为0时可能存在1种以上的凑法)
        dp[0][0]=1;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=m;j++)
            {
                if(j-nums[i-1]<0)//背包的空间不足,只能选择不装物品i
                    dp[i][j]=dp[i-1][j];
                else//装与不装,两种选择的结果之和
                    dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i-1]];
            }
        }
        return dp[n][m];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值