DP刷题练习(二)

DP刷题练习(二)

文章内容学习自代码随想录,感谢carl!!!!

文章目录

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()];
    }
相关推荐
董董灿是个攻城狮2 小时前
5分钟搞懂什么是窗口注意力?
算法
Dann Hiroaki2 小时前
笔记分享: 哈尔滨工业大学CS31002编译原理——02. 语法分析
笔记·算法
qqxhb4 小时前
零基础数据结构与算法——第四章:基础算法-排序(上)
java·数据结构·算法·冒泡·插入·选择
FirstFrost --sy6 小时前
数据结构之二叉树
c语言·数据结构·c++·算法·链表·深度优先·广度优先
森焱森6 小时前
垂起固定翼无人机介绍
c语言·单片机·算法·架构·无人机
搂鱼1145146 小时前
(倍增)洛谷 P1613 跑路/P4155 国旗计划
算法
Yingye Zhu(HPXXZYY)6 小时前
Codeforces 2021 C Those Who Are With Us
数据结构·c++·算法
无聊的小坏坏7 小时前
三种方法详解最长回文子串问题
c++·算法·回文串
长路 ㅤ   8 小时前
Java后端技术博客汇总文档
分布式·算法·技术分享·编程学习·java后端
秋说8 小时前
【PTA数据结构 | C语言版】两枚硬币
c语言·数据结构·算法