当前位置: 首页 > news >正文

DP刷题练习(一)

DP刷题练习(一)

作者:blue

时间:2024.8.5

文章目录

  • DP刷题练习(一)
      • [509. 斐波那契数 - 力扣(LeetCode)](https://leetcode.cn/problems/fibonacci-number/submissions/552475617/)
      • [70. 爬楼梯 - 力扣(LeetCode)](https://leetcode.cn/problems/climbing-stairs/)
      • [746. 使用最小花费爬楼梯 - 力扣(LeetCode)](https://leetcode.cn/problems/min-cost-climbing-stairs/)
      • [62. 不同路径 - 力扣(LeetCode)](https://leetcode.cn/problems/unique-paths/)
      • [63. 不同路径 II - 力扣(LeetCode)](https://leetcode.cn/problems/unique-paths-ii/description/)
      • [343. 整数拆分 - 力扣(LeetCode)](https://leetcode.cn/problems/integer-break/)
      • [96. 不同的二叉搜索树 - 力扣(LeetCode)](https://leetcode.cn/problems/unique-binary-search-trees/)
      • [416. 分割等和子集 - 力扣(LeetCode)](https://leetcode.cn/problems/partition-equal-subset-sum/)这个背包能不能装满???
      • 学有余力,手撕01背包:[P1048 [NOIP2005 普及组\] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)](https://www.luogu.com.cn/problem/P1048)
        • 二维数组版本:
        • 交替滚动版本:
        • 一维数组版本:

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

509. 斐波那契数 - 力扣(LeetCode)

int fib(int n) {int dp[31];//dp[i]表示第i个数的值dp[0]=0,dp[1]=1;for(int i=2;i<=n;i++) dp[i]=dp[i-1]+dp[i-2];return dp[n];}

70. 爬楼梯 - 力扣(LeetCode)

int climbStairs(int n) {int dp[46];//dp[i]表示,爬到第i层楼梯,有dp[i]种方案dp[1]=1,dp[2]=2;for(int i=3;i<=n;i++) dp[i]=dp[i-1]+dp[i-2];return dp[n];}
//递推公式怎么来?
//你想上到第10层,那你前一步是什么?
//肯定你是从第9层爬一步或者是从第8层爬两步上来,那么爬上第10层的方案就是,爬上第9层的方案+爬上第8层的方案
//∴dp[i]=dp[i-1]+dp[i-2],将最初始的dp[1]=1,dp[2]=2;

746. 使用最小花费爬楼梯 - 力扣(LeetCode)

int minCostClimbingStairs(vector<int>& cost) {int n=cost.size();int dp[n+1];//dp[i]表示,爬到第i层的最小花费dp[0]=0,dp[1]=0;//如果是爬到0/1就不用钱,因为0/1都可以作为初始点for(int i=2;i<=n;i++){dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);}return dp[n];//顶楼是n}//要注意题目的要求,题目是说,你一次花费当下这一层,注意是当层!的钱,你就可以向上爬1/2层
//所以你爬到第i层,可以是从i-1层来花费cost[i-1],也可以是从i-2层来花费cost[i-2],二者取最小值就是爬上第i层的最小花费
//所以递推公式就是 dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
//要注意dp数组的初始化,你可以选择从第0层和第1层开始爬,所以爬到第0/1层的花费都是0

62. 不同路径 - 力扣(LeetCode)

 int uniquePaths(int m, int n) {int dp[m][n];//dp[i][j]表示从起点(0,0)走到(i,j)的不同路径数dp[0][0]=1;for(int i=1;i<=n-1;i++) dp[0][i]=1;for(int i=1;i<=m-1;i++) dp[i][0]=1;for(int i=1;i<=m-1;i++){for(int j=1;j<=n-1;j++) dp[i][j]=dp[i-1][j]+dp[i][j-1];}return dp[m-1][n-1];}
//动态规划五步曲
//dp含义:dp[i][j]表示从起点(0,0)走到(i,j)的不同路径数//递推公式;dp[i][j]=dp[i-1][j]+dp[i][j-1],为什么是这样,你想,你每一步只能从上方一格,或者是左方一格走来
//那么这一格的方案数,不就是上方一格的dp[i-1][j]加上左方一格的dp[i][j-1]//初始化:你想第一行往右的这一排,是不是只能从第一格往右走走到,那他们的路径数不就只能是从左往右吗?所以初始化为1
//同理你你一列往下这一排只能从第一格往下走走到,那他们的路径数不就只能是从上往下吗?所以初始化为1
//那对于(0,0)呢?也是只能是1,你不走就已经到达终点不也是一种方案吗?//遍历顺序:自不必多说,先知道前面的才能知道后面的,所以是从前往后遍历

63. 不同路径 II - 力扣(LeetCode)

int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m=obstacleGrid.size();int n=obstacleGrid[0].size();//获取行和列的长度if(obstacleGrid[0][0]==1||obstacleGrid[m-1][n-1]==1) return 0;//如果障碍物在起点或者终点,那路径数无论如何都是0int dp[m+10][n+10];dp[0][0]=1;int i;for(i=1;i<=n-1;i++){//行初始化if(obstacleGrid[0][i]==0) dp[0][i]=1;else break;}for(int j=i;j<=n-1;j++) dp[0][j]=0;
//for(i=1;i<=m-1;i++){//列初始化if(obstacleGrid[i][0]==0) dp[i][0]=1;else break;}for(int j=i;j<=m-1;j++) dp[j][0]=0;
/for(int j=1;j<=m-1;j++){for(int k=1;k<=n-1;k++){if(obstacleGrid[j][k]==0) dp[j][k]=dp[j-1][k]+dp[j][k-1];else dp[j][k]=0;}}return dp[m-1][n-1];}//别看本题代码写的如此多,其实并不复杂,我们一步一步来分析
//还是动规五步曲,在这里我设obs为障碍物数组
//dp含义:dp[i][j]表示从(0,0)->(i,j)的不同路径数//递推公式:①obs[i][j]==0时 dp[i][j]=dp[i-1][j]+dp[i][j-1]
//         ②obs[i][j]==1时 dp[i][j]=0//初始化:以上两步和上道题大同小异,本题初始化才是最讲究的部分
//首先如果终点或者起点就已经是障碍物了,那么肯定出发不了,肯定到达不了,路径数就是0,直接返回0即可
//其次第一行和第一列依然是最特殊的,在没有遇到障碍物之前,第一行/列上的这些点都是可达的,直接初始化dp[0][i]/dp[i][0]=1
//但是一旦遇到了障碍物,那么障碍物后面点都是不可达的,为什么?不要忘记了题目说的,你只能向右或者向下走!所以第一行只能从右来
//你一旦遇到障碍物,后面就不能到达了,同理,第一列也一样
//所以一旦遇到障碍物,我们就初始化障碍物的位置及其后面的位置的方案数为0!!!//遍历顺序:自然也是从前往后,因为只有前面的先知道了,后面的才能知道!!!!!

343. 整数拆分 - 力扣(LeetCode)

数的拆分是dp中常见的类型

1.dp数组的含义:dp[i]表示,将i拆分,得到的最大值为dp[i]

2.递推公式:我们固定j(从1开始枚举到i-1),那么针对每一种情况,我们都可以将其拆分为2个数,或者是n个数的乘积

为什么这样分呢?因为拆成两个数是最基本的,n个数可以递归得到

int a=j*(i-j);//拆两个数
int b=j*dp[i-j];//拆两个数以上,递归思想!
//所以我的递推公式就是
dp[i]=max(dp[i],max(a,b));
//为什么要将dp[i]也放进来比较,因为我们是枚举j,也就是枚举拆分的第一位数!这有多种情况,而我们要的是最大的那个!

3.初始化:

0与1不必说,不用拆分,自然初始化为0

dp[2]=1,的话无法用之前的基础步得到,所以2也得初始化

//3可以拆成,1*2,1*dp[2],2*1,2*dp[1] 所以3可以由之前的情况得到故3不用初始化dp[0]=0,dp[1]=0;//无法拆分dp[2]=1;//只能拆1*1

4.遍历顺序:自是从前往后,先知道前面的才能知道后面的

 int integerBreak(int n) {int dp[n+10];memset(dp,0,sizeof(dp));//注意这里先给所有赋值为0,因为我们一开始用dp[i]去比较,dp[i]中是没有值的,这样会报错dp[0]=0,dp[1]=0;//无法拆分dp[2]=1;//只能拆1*1for(int i=3;i<=n;i++){for(int j=1;j<i;j++){int a=j*(i-j);//拆两个数int b=j*dp[i-j];//拆两个数以上,递归思想!dp[i]=max(dp[i],max(a,b));}}return dp[n];}

小优化:

我们将数拆分,然后要使拆分后这些数的乘积最大,那么我们就要尽量使这些数相等,所以你拆一半就够了,后面你拆数就是向着大小差异的极端去的,所以可以这样优化

for(int j=1;j<=i/2;j++)
int integerBreak(int n) {int dp[n+10];memset(dp,0,sizeof(dp));dp[0]=0,dp[1]=0;//无法拆分dp[2]=1;//只能拆1*1for(int i=3;i<=n;i++){for(int j=1;j<=i/2;j++){int a=j*(i-j);//拆两个数int b=j*dp[i-j];//拆两个数以上,递归思想!dp[i]=max(dp[i],max(a,b));}}return dp[n];}

96. 不同的二叉搜索树 - 力扣(LeetCode)

首先何为二叉搜索树,节点的要求是针对于顶点来说,顶点左侧要小于顶点,顶点的右侧要大于顶点,就是要满足左小右大

1.dp数组的含义:dp[i],表示节点数目为i时,共有dp[i]种不同的二叉搜索树

2.递推公式:我们先用i枚举节点个数,用j表示j节点作为二叉搜索树的顶点为j,那么想想看,我们如何计算以j为顶点的二叉搜索树的种数呢?

是不是左子树的种数*右子树的种数,那么左子树有几种?是不是dp[j-1]种,你就想一串数,1,2,3,4,5此时j=4,那么比4小的,不就只有3个节点,而比4大的,不就是节点总数i,减掉j,5-4=1

然后j,有i种情况,就是说每个节点都可以作为顶点,所以都要把这i种情况累加起来

于是状态转移方程:dp[i]+=dp[j-1]*dp[i-j]

3.初始化:dp[0]=1,为什么?空树也是二叉搜索树!!

dp[1]要不要初始化?dp[1]+=dp[0]*dp[0]=1,符合现实情况,可以由递推公式得到,所以不用初始化

int numTrees(int n) {int dp[n+10];//表示i个节点,有dp[i]种不同的二叉搜索树memset(dp,0,sizeof(dp));//因为一会要自加,所以先给初值赋0dp[0]=1;//空树也是二叉搜索树for(int i=1;i<=n;i++){for(int j=1;j<=i;j++){dp[i]+=dp[j-1]*dp[i-j];//dp[j-1]左子树总数}                          //dp[i-j]右子树总数}return dp[n];}

416. 分割等和子集 - 力扣(LeetCode)这个背包能不能装满???

class Solution {
public:bool canPartition(vector<int>& nums) {int n=0;//背包容量int sum=0;int m=nums.size();//物品数量for(int i=0;i<m;i++) sum+=nums[i];n=sum/2;if(sum-n!=n) return false;//特判,不可能等分的情况int dp[m][n+1];//dp[i][j]针对前(0-i)个物品用容量j的背包来存放能否放满//初始化for(int i=0;i<m;i++) dp[i][0]=0;//容量为0什么都放不了for(int i=0;i<=n;i++){//第0个物品放置情况if(i>=nums[0]) dp[0][i]=nums[0];else dp[0][i]=0;}for(int i=1;i<m;i++){//物品for(int j=1;j<=n;j++){//容量if(j>=nums[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-nums[i]]+nums[i]);else dp[i][j]=dp[i-1][j];}}if(dp[m-1][n]==n) return true;else return false;}
};

学有余力,手撕01背包:[P1048 NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

0/1背包要注意,所有的物品只能选择一次!!!

二维数组版本:
#include <iostream>
using namespace std;
int dp[110][1010];//dp[i][j]表示在j时间内任采(0,i)株草药可以获得的最大价值 
int t[110],m[110];
int main()
{int T,M;cin>>T>>M;for(int i=0;i<M;i++) cin>>t[i]>>m[i];//初始化:非常重要!!! for(int i=0;i<M;i++) dp[i][0]=0;//0秒什么都放不了 for(int i=0;i<=T;i++){ //注意时间可以到达第T秒 if(i>=t[0]) dp[0][i]=m[0]; //看下第一个物品能不能放 else dp[0][i]=0;}for(int i=1;i<M;i++){ for(int j=1;j<=T;j++){ //注意时间可以到达第T秒 if(j>=t[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-t[i]]+m[i]);//能放,看下放值不值 else dp[i][j]=dp[i-1][j];//没法放 }}cout<<dp[M-1][T];//注意时间可以到达第T秒 return 0;	
} 
交替滚动版本:

经过观察可以发现,dp[i][j]的更新只与dp[i-1]的状态有关,那么我们只保留两行就够了,每一次根据第一行来更新第二行的信息,然后再把第二行替换为第一行。

#include <iostream>
using namespace std;
int dp[2][1010];//dp[0/1][j]表示在j时间内可以获得的最大价值
//0表示旧数据,1表示新数据 
int t[110],m[110];
int main()
{int T,M;cin>>T>>M;for(int i=0;i<M;i++) cin>>t[i]>>m[i];//初始化:非常重要!!! for(int i=0;i<=T;i++){ //注意时间可以到达第T秒 if(i>=t[0]) dp[0][i]=m[0]; //看下第0个物品能不能放 else dp[0][i]=0;}int now=0,old=1;//我这样写是为了使代码具有一般性 for(int i=1;i<M;i++){ swap(old,now);//新老交替for(int j=1;j<=T;j++){ //注意时间可以到达第T秒 if(j>=t[i]) dp[now][j]=max(dp[old][j],dp[old][j-t[i]]+m[i]);//能放,看下放值不值 else dp[now][j]=dp[old][j];//没法放 }}cout<<dp[now][T];//注意时间可以到达第T秒 return 0;	
} 
一维数组版本:
/*这里的遍历顺序非常有考究,首先为什么时间是从右往左遍历呢?
dp[i][j]的更新只与dp[i-1][j]和dp[i-1][j-weight_i]左上角这两个数据有关,而与右边的数据无关,
那么从右向左遍历,遍历时左边的数据还是上一行的数据没有更新,这样我们就可以实现自我滚动,为什么j到t[i]就可停下来,因为只有j>=t[i]时才有可能交换,而j<t[i]时保留原来的值就可以了
为什么,要先物品后时间呢?
如果你先时间后物品,你看你每次dp[j]里的值,不就永远只有1个物品吗?你每次上个物品的值都被这次的替换了,脱离了我们
dp[j]的含义!!!!*/
#include <iostream>
using namespace std;
int dp[1010];//dp[j]表示在j时间内可以获得的最大价值int t[110],m[110];
int main()
{int T,M;cin>>T>>M;for(int i=0;i<M;i++) cin>>t[i]>>m[i];//初始化:非常重要!!! for(int i=0;i<=T;i++){ //注意时间可以到达第T秒 if(i>=t[0]) dp[i]=m[0]; //看下第0个物品能不能放 else dp[i]=0;}for(int i=1;i<M;i++){ //先物品后时间for(int j=T;j>=t[i];j--){ //注意时间可以到达第T秒 dp[j]=max(dp[j],dp[j-t[i]]+m[i]);}}cout<<dp[T];//注意时间可以到达第T秒 return 0;	
} 

相关文章:

  • 软件工程的相关名词解释
  • PostgreSQL的扩展pg_visibility
  • BeckHoff <---> Keyence (LJ-X8000) 2D相机 Profinet 通讯
  • # 把 ISO 写入 U 盘(相关了解)
  • 《深度学习:基础与概念》第一章 学习笔记与思考
  • VBA使用字典统计
  • 键盘按键枚举 Key 说明文档
  • [创业之路-418]:经济学 - 凯恩斯主义的需求管理与西方供应侧理论、供需关系理论详解以及对创者者的启示
  • 【leetcode】78. 子集
  • 湖北理元理律师事务所债务规划方法论:法律框架下的可持续还款体系
  • 义乌购商品详情接口开发指南
  • 详解Java的启动参数-Dfile.encoding、System.getProperty(“file.encoding“)、该参数影响哪些行为
  • Atcoder Beginner Contest 410 题解报告
  • 生产者消费者消息流转和基本实操
  • Golang 在 Linux 平台上的并发控制
  • Vue3 + TypeScript + Element Plus 表格实例null检查方法
  • 18.vue.js的scoped样式隔离?原理和使用?(1)
  • c++总结-05-模板与泛型编程
  • 大IPD之——学习华为市场洞察,为战略找到方向(四)
  • 层压板选择、信号完整性和其他权衡
  • 营销型网站建设专家/app 推广
  • 外国人可以在中国做网站吗/seo整站优化费用
  • 外卖网站那家做的好/免费网络推广的方法
  • 网站开发和推广的不同/军事新闻 今日关注
  • 安丘网站建设报价/淘宝关键词指数
  • 太原哪家网站建设公司好/网络营销的模式有哪些?