算法第31天|动态规划:最后一块石头的重量Ⅱ、目标和、一和零
今日总结:
1、最后一块石头的重量:
需要明确:dp[j]表示的是在容量为j的条件下,所能达到的最大的价值,这个题目中,价值就是石头重量,重量(所占容量)也是石头重量。
所以就是在计算:在重量容量为sum/2时,能不能达到最大的重量,所以dp[sum/2]<=sum/2,又因为sum/2是向下取整,所以另一半一定>=sum/2
所以最终结果: return (sum -dp[sum/2])-dp[sum/2];
2、目标和
需要思考如何得出的01背包问题,dp数组、下标、重量、价值、递推公式等
3、一和零
需要思考如何得出的01背包问题,dp数组、下标、重量、价值、递推公式等
最后一块石头的重量Ⅱ
题目链接:1049. 最后一块石头的重量 II - 力扣(LeetCode)
代码随想录
整体思路:
跟前一个题进行对比(分割等和子集)
1、分割等和子集,最终的目的是分成两个子集,使子集的和相同,也就是说:每个子集的和都必须是sum/2才能满足要求。
背包问题:一个背包有最大容量,如何装若干物品,去获得最大的价值
背包容量:达到sum/2就是最大的容量
物品:集合中的元素
物品价值:元素本身的值
物品重量:元素本身的值
dp[j]:表示在容量为j条件下,所能达到的最大价值(最大的和 dp[j]<=j,因为j就是sum/2,dp[j]就是在sum/2下,能达到的最大的和)
如果在dp[sum/2]<sum/2,就说明分割失败
2、最后一块石头的重量,任意两块石头作比较,相同就粉碎,不同就剩下大的减去小的重量-->可以将石头数组分成两份,尽量相同,然后大的份-小的份就是最后一块的质量
相当于分割等和子集,但是会出现最后两个不相同的情况,需要额外判断
背包容量:寻找的是sum/2
物品:石头元素
物品价值:价值就是在比较重量问题-->重量
物品重量:就是石头重量
dp[j]:表示的是在容量为j条件下,所能达到的最大的重量(dp[j]<=j)
整体代码:
class Solution { public:int lastStoneWeightII(vector<int>& stones) {int sum =0;for(int i=0;i<stones.size();i++){sum +=stones[i];}int target = sum/2;//获取最大容量vector<int>dp(30000,0);for(int i=0;i<stones.size();i++){for(int j=target;j>=stones[i];j--)//只要当前的容量大于所遍历的这一次的重量,就可以容纳dp[j-stones[i]]+value,就可以判断是不是dp[j]变大了{dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]);}}//因为sum可能是奇数,所以sum/2就肯定小于一般是向下取整//所以最终返回的石头重量:return sum -dp[target] - dp[target];} };
目标和
题目链接:494. 目标和 - 力扣(LeetCode)
代码随想录
整体思路:
//将加法加起来left,减法加起来(绝对值 )right
left - right = target
left + right = sum
left - (sum -left) =target
left = ( target +sum )/2
所以问题可以转化为在nums中寻找有多少方法和为left,每个数只能选择取或者不取-->01背包问题
1、确定dp[j]及下标的含义:
dp[j]的含义是在容量(nums[i])为j的条件下,能够达到目标容量j的最多的方法(注意审题:求得是方法)
容量j :left
物品价值nums[i] 就是整数值
物品重量nums[i] 也是整数值
2、确定dp数组的递归公式(状态转移方程)
可将整体分为:取当前物品i+不取当前物品i
取物品i一定要求的是当前容量j>物品i的重量
同时,因为是一维滚动数组,所以必须遍历重量从后往前,也就是说当前的dp[j] = 之前的dp[j]+之前的dp[j-nums[i]]整体代码:
class Solution { public:int findTargetSumWays(vector<int>& nums, int target) {//将加法加起来left,减法加起来(绝对值 )right//left - right = target//left + right = sum//left - (sum -left) =target//left = ( target +sum )/2//所以问题可以转化为在nums中寻找有多少方法和为left,每个数只能选择取或者不取-->01背包问题//1、确定dp[j]及下标的含义://dp[j]的含义是在容量(nums[i])为j的条件下,能够达到目标容量j的最多的方法(注意审题:求得是方法) //容量j :left//物品价值nums[i] 就是整数值//物品重量nums[i] 也是整数值//2、确定dp数组的递归公式(状态转移方程)//可将整体分为:取当前物品i+不取当前物品i//取物品i一定要求的是当前容量j>物品i的重量//同时,因为是一维滚动数组,所以必须遍历重量从后往前,也就是说当前的dp[j] = 之前的dp[j]+之前的dp[j-nums[i]]int sum =0;for(int i=0;i<nums.size();i++) sum +=nums[i];if((target+sum)%2==1)return 0;if(abs(target)>sum) return 0;int left = (target+sum)/2;vector<int> dp(left+1,0);//最大容量设置//初始化,与二维数组不同,只需要设置dp[0]dp[0] =1;for(int i=0;i<nums.size();i++){for(int j=left;j>=nums[i];j--)//只需要看大于当前物品的部分,从后往前:当前的dp[j]用的是之前的数据,而不是这次修改的数据{dp[j] = dp[j] + dp[j-nums[i]];}}return dp[left];} };
一和零
题目链接:474. 一和零 - 力扣(LeetCode)
代码随想录
整体思路:
对于整体来说,是从二进制字符串数组strs中选取若干个str,使得子集中拥有最多m个0和n个1
对于每一个str必须是选或者不选-->01背包问题
物品:每个字符串
物品重量:占据的0、占据的1,两个维度的重量
物品价值:1,每个物品都是一个小的子集
1、确定dp[i][j]及下标:
dp[][]表示在0容量为i,1容量为j的条件下,最大的子集
i表示0的容量
j表示1的容量
2、确定dp的递推公式(状态转移方程)
分为取当前的str、不取当前的str
取当前的str,必须要求i>str中的0的数量,j>str中的1的数量 dp[i][j] = dp[i-zeronum][j-onenum] +1
不取当前的str dp[i][j] = dp[i][j];整体代码:
class Solution { public:int findMaxForm(vector<string>& strs, int m, int n) {//对于整体来说,是从二进制字符串数组strs中选取若干个str,使得子集中拥有最多m个0和n个1//对于每一个str必须是选或者不选-->01背包问题//物品:每个字符串//物品重量:占据的0、占据的1,两个维度的重量//物品价值:1,每个物品都是一个小的子集//1、确定dp[i][j]及下标://dp[][]表示在0容量为i,1容量为j的条件下,最大的子集//i表示0的容量//j表示1的容量//2、确定dp的递推公式(状态转移方程)//分为取当前的str、不取当前的str//取当前的str,必须要求i>str中的0的数量,j>str中的1的数量 dp[i][j] = dp[i-zeronum][j-onenum] +1//不取当前的str dp[i][j] = dp[i][j];vector<vector<int>>dp(m+1,vector<int>(n+1,0));for(auto str : strs){int zeronum =0,onenum=0;for(auto c :str){if(c=='0')zeronum++;else onenum++;}for(int i=m;i>=zeronum;i--){for(int j=n;j>=onenum;j--){dp[i][j] = max(dp[i-zeronum][j-onenum] +1,dp[i][j]);}}}return dp[m][n];} };