代码随想录训练营Day43 | Leetcode 1049、494、474
一、1049 最后一块石头的重量II
题目链接:1049 最后一块石头的重量II
核心:建模成01背包问题,所有石头两两相减,要求最后剩下的重量最小,也就是将所有石头尽可能“等分”成2组,如果完全等分那么剩下重量为0,是理想情况;如果不能恰好等分就分成一组为sum/2(等分即整除情况,非整除情况取其小值,另一组为sum-sum/2)。因此背包最大容量是sum/2,物品价值即石头重量。
dp[j]:背包容量j能包含的石头的最大重量,dp[sum/2]==sum/2说明恰好等分。
int lastStoneWeightII(vector<int>& stones) {
//两两相减最终得到最小的差值,实质是将所有元素划分成两组子集,且子集之和差值最小,即2组子集之和尽可能相等,与分割等和子集问题类似
vector<int> dp(15001,0); //dp[i]:容量i的背包所拥有的最大重量
int sum=0; //记录所有元素之和
for(int stone:stones)
sum+=stone;
int target=sum/2; //最佳情况是子集差值为0,即每个子集之和是总和的一半
for(int i=0;i<stones.size();++i)
{//先遍历物品,即依次将各个物品放入背包,确定不同容量j的背包的最大重量
for(int j=target;j>=stones[i];--j) //再遍历背包容量,容量小于物品重量则无法放置
dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
}//dp[target]表示背包容量是target,所包含石头元素的最大重量,如果等于target说明恰好等分
return sum-dp[target]-dp[target]; //dp[target]是“等分”子集之和,sum-dp[target]是另一个子集之和
}
二、494 目标和
题目链接:494 目标和
核心:建模成01背包问题,要求所有元素之和(包括差)为target,即两组子集之差为target:left-right=target,且这两组子集之和为所有元素之和sum。因此我们要求解的target的方法数就转换成子集之和为left的不同方法数。
综上,背包最大容量为left=(sum+target)/2;物品即数组内各元素;
dp[j]:背包容量为j(子集之和为j)时,不同的组合数;
递推公式:涉及到组合问题,一般是累加,比如:dp[3]的组合数包括,第一,给定元素1,那么dp[2]是其中一种组合方法;第二,给定元素2,dp[1]是其中一种组合方法;第三,给定元素3,dp[0]是其中一种组合方法。以上三种都是dp[3]的组合方法,因此需要累加上述不同的组合。
int findTargetSumWays(vector<int>& nums, int target) {
//转换成两组子集(left、right)之差为target,之和为sum,那么子集left的不同构造数也就是元素和为target的不同方法数
int sum=0;
for(int num:nums)
sum+=num; //元素之和
if(abs(target)>sum)
return 0;
if((target+sum)%2==1)
return 0; //无法整除说明left是近似值,此时right=sum-left无法满足left-right=target
int left=(target+sum)/2; //背包最大容量——子集left之和
vector<int> dp(left+1,0); //dp[i]:装满容量为i的背包有dp[i]种不同方法
dp[0]=1; //初始化,容量为0的背包有1种方法
for(int i=0;i<nums.size();++i)
{//先遍历物品,即数组各元素;再遍历背包容量j
for(int j=left;j>=nums[i];--j)
dp[j]+=dp[j-nums[i]]; //递推公式:比如容量j的组合方法是累加的,组合通常都是累加
}
return dp[left];
}
三、474 一和零
题目链接:474 一和零
核心:建模成01背包问题,本题的m和n都是背包的容量,是2个不同维度的容量,而物品是字符串数组中的字符串。
int findMaxForm(vector<string>& strs, int m, int n) {
//背包容量有2个维度m和n,即给定背包容量使得装满背包最多可有多少物品(子集)
//dp[i][j]:背包最多有i个0和j个1,最多可包含的子集个数
//递推公式:由上一个子集可推导到当前子集,且保留最大值
vector<vector<int>> dp(m+1,vector<int> (n+1,0));
//dp[0][0]=0; //初始化为0
for(string str:strs)
{//遍历字符串数组,即子集(物品)
int zeronum=0;
int onenum=0;
for(char ch:str)
{//遍历某个字符串各字符,统计0和1的个数
if(ch=='0')
zeronum++;
else
onenum++;
}
for(int i=m;i>=zeronum;--i)
{//遍历背包容量第一个维度:0的个数,依然是倒序遍历
for(int j=n;j>=onenum;--j)
{//遍历背包容量第二个维度:1的个数
dp[i][j]=max(dp[i][j],dp[i-zeronum][j-onenum]+1);
}
}
}
return dp[m][n]; //返回背包容量最多有m个0和n个1可包含子集的最大数目
}