416. 分割等和子集
题目描述
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。
思路分析
本题要求集合里能否出现总和为 sum / 2 的子集。
只有确定了如下四点,才能把01背包问题套到本题上来。
背包的体积为sum / 2
背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
背包如果正好装满,说明找到了总和为 sum / 2 的子集。
背包中每一个元素是不可重复放入。
动规五部曲
确定dp数组以及下标含义
01背包中,dp[j]表示:容量为j的背包,所背的物品最大价值为dp[j].
回到本题,dp[j]表示:背包总容量为j,最大可以凑成j的子集总和为dp[j]
确定递推公式
本题中,背包中放入数值,那么物品i的重量是nums[i],其价值也是nums[i]
所以递归公式: dp[j] = max(dp[j],dp[j-nums[i]]+nums[i])
3.dp数组如何初始化
当背包容量为0时,放入数值的总和为0,所以 dp[0] = 0
确定遍历顺序
根据卡尔大佬的 动态规划:关于01背包问题,你该了解这些!(滚动数组) 的讲解,如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历.
举例推导dp数组
参考代码
#include<bits/stdc++.h> using namespace std; 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(10001,0);//定义dp dp[0] = 0;//初始化dp[0] for(int i = 0; i < nums.size(); i++) { //遍历集合所有数 for(int j = target; j >=nums[i]; j--) { //遍历背包大小. 只有背包体积>=num[i],当前价值才可能更新,否则和上一个一样. dp[j] = max(dp[j],dp[j-nums[i]] + nums[i]); } } if(dp[target]==target) { //如果背包可以放下target的数,则说明可以分割. return true; } return false; }
1049. 最后一块石头的重量 II
题目描述
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。
示例 1:
输入:stones = [2,7,4,1,8,1] 输出:1 解释: 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], 组合 2 和 1,得到 1,所以数组转化为 [1,1,1], 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
示例 2:
输入:stones = [31,26,33,21,40] 输出:5
示例 3:
输入:stones = [1,2] 输出:1
思路分析
本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
这就和上面的 416. 分割等和子集 (opens new window) 非常像了。
动归五部曲
确定dp数组以及下标含义
dp[j]表示容量为j的背包,最多可以背dp[j]这么重的石头
确定递推公式
本题和上题类似,上题是子集一半,本题是一半石头. 递推公式: dp[j] = max(dp[j],dp[j-nums[i]] + nums[i]);
dp数组如何初始化
提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000 。
而我们要求的target其实只是最大重量的一半,所以dp数组开到15000+1大小就可以了。
然后是dp初始化,因为重量都是 > 0,所以dp[0] = 0
确定遍历顺序
根据卡尔大佬的 动态规划:关于01背包问题,你该了解这些!(滚动数组) 的讲解,如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历.
举例推导dp数组
以两个例子演示dp数组的推导过程,如下:
参考代码
#include<bits/stdc++.h> using namespace std; //打印dp数组 void print(vector<int>& dp,int& num,int& target ){ cout<<"dp["<<num<<"]: "; for(int i = 0; i <= target;i++){ cout<<dp[i]<<" "; } cout<<endl; } int lastStoneWeightII(vector<int>& stones) { int sum = 0; for(int i = 0; i < stones.size(); i++) { sum+=stones[i]; } int target = sum / 2; // cout<<"石头之和为:"<<sum<<endl; vector<int> dp(15001,0); for(int i = 0;i < stones.size(); i++){ for(int j = target; j >=stones[i]; j--){ dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]) ; } // print(dp,i,target); //打印输出 } return sum-dp[target]-dp[target]; } int main(){ // vector<int> stones = {2,7,4,1,8,1}; vector<int> stones = {2,4,1,1}; lastStoneWeightII(stones); return 0; }