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];
}
};