DP刷题练习(二)
文章内容学习自代码随想录,感谢carl!!!!
文章目录
- DP刷题练习(二)
-
-
- [[1049. 最后一块石头的重量 II - 力扣(LeetCode)](https://leetcode.cn/problems/last-stone-weight-ii/description/)这个背包最多能装多少???](#1049. 最后一块石头的重量 II - 力扣(LeetCode)这个背包最多能装多少???)
- [[494. 目标和 - 力扣(LeetCode)](https://leetcode.cn/problems/target-sum/description/)装满这个背包有多少种方法????](#494. 目标和 - 力扣(LeetCode)装满这个背包有多少种方法????)
- [[474. 一和零 - 力扣(LeetCode)](https://leetcode.cn/problems/ones-and-zeroes/description/?envType=problem-list-v2\&envId=hiP3T2mg)装满这个背包最多要用多少物品????](#474. 一和零 - 力扣(LeetCode)装满这个背包最多要用多少物品????)
- [[518. 零钱兑换 II - 力扣(LeetCode)](https://leetcode.cn/problems/coin-change-ii/)完全背包装满有多少种方法???](#518. 零钱兑换 II - 力扣(LeetCode)完全背包装满有多少种方法???)
- [[377. 组合总和 Ⅳ - 力扣(LeetCode)](https://leetcode.cn/problems/combination-sum-iv/description/)](#377. 组合总和 Ⅳ - 力扣(LeetCode))
- [[322. 零钱兑换 - 力扣(LeetCode)](https://leetcode.cn/problems/coin-change/description/)完全背包装满要最少需要多少种物品???](#322. 零钱兑换 - 力扣(LeetCode)完全背包装满要最少需要多少种物品???)
- [[279. 完全平方数 - 力扣(LeetCode)](https://leetcode.cn/problems/perfect-squares/)](#279. 完全平方数 - 力扣(LeetCode))
- [[139. 单词拆分 - 力扣(LeetCode)](https://leetcode.cn/problems/word-break/)](#139. 单词拆分 - 力扣(LeetCode))
-
1049. 最后一块石头的重量 II - 力扣(LeetCode)这个背包最多能装多少???
石头相撞,我们想要最后得到一块最小质量的石头甚至没有,我们就要尽可能的把他分成两堆,那就是0/1背包
我们有一个一半总质量的背包尽可能装满他,然后最后拿这两堆相撞,返回剩下的值
这次我写的是一维数组版本:
cpp
int lastStoneWeightII(vector<int>& stones) {
int m=stones.size();//石头块数
int sum=0;
for(int i=0;i<m;i++) sum+=stones[i];
int n=sum/2;//尽可能分成相同的两堆
int dp[n+1];//dp[j]表示容量为j的背包所能装下的最大重量
for(int j=0;j<=n;j++){//初始化
if(j>=stones[0]) dp[j]=stones[0];
else dp[j]=0;
}
for(int i=1;i<m;i++){//先物品
for(int j=n;j>=stones[i];j--){//后背包
dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
}
}
int x=(sum-dp[n])-dp[n];
return x;
}
494. 目标和 - 力扣(LeetCode)装满这个背包有多少种方法????
cpp
样例:[1,1,1,1,1]
/*我们要使nums数组中的一部分为正,一部分为负,然后让他们的和等于target,这是我们的目标
我们将正数的这一部分记作left[],负数这一部分记作right[]
于是我们有:
left+right=sum ===>> right=sum-left
left-right=target
所以left-(sum-left)=target ===>> left=(target+sum)/2,这个数是非负数的和,如果是负数的话,不符合条件直接return 0就好
因为这里的都是非负整数集合,所以算出的(target+sum)%2如果不能整除的话,那就永远的凑不出target
直接return 0;
*/
所以本题就演变成给你一个容量为left的背包问你,装满这个背包总共有多少种方案?依然是0/1背包问题
cpp
int findTargetSumWays(vector<int>& nums, int target) {
int sum=0;
for(int i=0;i<nums.size();i++) sum+=nums[i];
//(sum+taget)/2是+号元素之和,必须是非负数
if((sum+target)%2!=0||(sum+target)/2<0) return 0;
int bag_size=(sum+target)/2;
int dp[bag_size+10];//dp[j]表示用数组中若干个元素组成的和为j的方案数
memset(dp,0,sizeof(dp));
dp[0]=1;
for(int i=0;i<nums.size();i++){
for(int j=bag_size;j>=nums[i];j--){
dp[j]=dp[j]+dp[j-nums[i]];//原来装满的方案,加上+上这个数才装满的方案
}
}
return dp[bag_size];
}
所以我们可以把状态定义为 dp [ j ],表示用数组中若干个元素组成和为j的方案数。那么状态转移方程就是:
cpp
dp[j] = dp[j] + dp[j - nums[i]];
这个方程的意思是,如果我们要用若干个元素组成和为j的方案数,那么有两种选择:不选第i个元素或者选第i个元素。如果不选第i个元素,那么原来已经有多少种方案数就不变;如果选第i个元素,那么剩下要组成和为j - nums[i], 的方案数就等于dp[j - nums[i]]。所以两种选择相加就是dp【j】。
但是在实现这个状态转移方程时,有一个细节需要注意:由于每次更新dp【j】都依赖于之前计算过得dp值(也就是说当前行依赖于上一行),所以我们必须从后往前遍历更新dp值(也就是说从右往左更新),否则会覆盖掉之前需要用到得值。
最后返回dp【bag_size】即可。
474. 一和零 - 力扣(LeetCode)装满这个背包最多要用多少物品????
本题让我们找出一个满足m个0,n个1的最大子集
我们将问题抽象为一个背包,将问题转化为,当背包可以装m个0,n个1时装满这个背包最多能用多少种物品
cpp
dp[i][j]//背包能装i个0,j个1时,最多要用dp[i][j]个物品
cpp
//设这个物品有x个0,y个1,那么递推式就可以为
dp[i][j]=max(dp[i-x][j-y]+1,dp[i][j]);
cpp
int findMaxForm(vector<string>& strs, int m, int n) {
int dp[110][110];//dp[i][j]表示,当背包能装i个0,j个1的时候,最多能装dp[i][j]个物品
memset(dp,0,sizeof(dp));
for(string str:strs)
{
int x=0,y=0;
for(char c:str)
{
if(c=='0') x++;
else y++;
}
for(int i=m;i>=x;i--)
{
for(int j=n;j>=y;j--)
{
dp[i][j]=max(dp[i-x][j-y]+1,dp[i][j]);
}
}
}
return dp[m][n];
}
总结:以上几道题,全都是0/1背包问题,0/1背包问题的特征是物品只能用一次
由此演变出的四类问题:这个背包能不能装满?这个背包最多能装多少?装满这个背包有多少种方案?装满这个背包最多要用多少种物品?
需要我们勤加练习!!!!接下来是完全背包问题!
518. 零钱兑换 II - 力扣(LeetCode)完全背包装满有多少种方法???
本题求得还是一种组合数,顺序是无关的!注意本题只能先遍历物品后遍历背包,因为只有先遍历物品后遍历背包,你得出来的才是组合数!否则你得出来的就是排列数,而排列数,是考究顺序的!!!
cpp
int change(int amount, vector<int>& coins) {
int dp[amount+10];//dp[j]表示,用coins中任意硬币能组成j的最大方案数为dp[j]
memset(dp,0,sizeof(dp));
dp[0]=1;//组合成0块钱,有一种方案
for(int i=0;i<coins.size();i++)//先物品再容量
for(int j=coins[i];j<=amount;j++){//完全背包是从前往后遍历,为什么?因为物品可以无限用,所以你当前的状态
dp[j]=dp[j]+dp[j-coins[i]]; //是由你已经往里放了这个东西转移而来的,意味你可以再放一次
}
return dp[amount];
}
组合数:先遍历物品后遍历背包是这样,比如,外层循环固定coins【1】,在内层循环遍历背包时,随着背包不断增加,coins【1】可以重复被添加进来,而由于外层循环固定了,因此coins【2】只能在下一次外层循环添加进不同大小的背包中,这么看的话,coins【i+1】只能在coins【i】之后了;只有1,2,3,4的情况!!!
排列数:如果先遍历背包后遍历物品,那么外层循环先固定背包大小j,然后在大小为j的背包中循环遍历添加物品,然后在下次外层循环背包大小变为j+1,此时仍要执行内层循环遍历添加物品,也就会出现在上一轮外层循环中添加coins【2】的基础上还能再添加coins【1】的情况,那么就有了coins【1】在coins【2】之后的情况了。会出现1,2 ;2,1的情况!!!
377. 组合总和 Ⅳ - 力扣(LeetCode)
像这道题他遍历的就是排列数,因为他将1,2 |2,1区分为两个不同的组合,所以应该先遍历容量,再遍历数量
cpp
int combinationSum4(vector<int>& nums, int target) {
unsigned long long dp[1010];
memset(dp,0,sizeof(dp));
dp[0]=1;
for(int j=1;j<=target;j++)//先遍历容量
for(int i=0;i<nums.size();i++){//再遍历物品,求的是排列数
if(j>=nums[i]) dp[j]=dp[j]+dp[j-nums[i]];
}
return dp[target];
}
322. 零钱兑换 - 力扣(LeetCode)完全背包装满要最少需要多少种物品???
这道题是求物品可以无限放,装满背包最少需要多少枚硬币
cpp
int coinChange(vector<int>& coins, int amount) {
int dp[amount+10];//dp[j]表示,装满容量为j的背包,最少需要dp[j]个物品
memset(dp,0x3f,sizeof(dp)); //此处初始化为最大值是服务于min
dp[0]=0; //初始化成0是题目说,输入:coins = [1], amount = 0 输出:0
for(int i=0;i<coins.size();i++)
{
for(int j=1;j<=amount;j++)
{
if(j>=coins[i]) dp[j]=min(dp[j],dp[j-coins[i]]+1);
}
}
if (dp[amount]>=0x3f3f3f) return -1;
else return dp[amount];
}
279. 完全平方数 - 力扣(LeetCode)
我们看数据大小,先确定完全平方数超不过100*100,自己拼凑一个序列,然后秒了
cpp
int numSquares(int n) {
int nums[110];
for(int i=1;i<=100;i++) nums[i]=i*i;
int dp[n+1];//dp[j]表示,装满容量为j的背包,使用的最小完全平方数的数量为dp[j]
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(int i=1;i<=100;i++)
{
for(int j=1;j<=n;j++)
{
if(j>=nums[i]) dp[j]=min(dp[j],dp[j-nums[i]]+1);
}
}
return dp[n];
}
139. 单词拆分 - 力扣(LeetCode)
如果dp【j】就是dp【i】前面的字段可以被拼接,并且 i-j到i这段也可以被word表达,那么整个长度为i的字段就是可以被拼接的直接跳出,不能再做更新!!
cpp
bool wordBreak(string s, vector<string>& wordDict) {
set<string> word;
for(string w:wordDict){
word.insert(w);
}
vector<int> dp(s.size()+1,false);//dp[i]表示,长度为i的字符串能否被字典中的单词组成为dp[i]
dp[0]=true;
for(int i=1;i<=s.size();i++){ //串的长度,这里我们必须先遍历背包,因为我们处理的是排列,物品顺序是有影响的
for(int j=0;j<i;j++){ //串前面的长度
if(dp[j]&&word.find(s.substr(j,i-j))!=word.end())
{
dp[i]=true;
break;//可以拼接就可以跳出了
}
}
}
return dp[s.size()];
}