算法第29天|动态规划dp2:不同路径、不同路径Ⅱ、整数拆分、不同的二叉搜索树
今日总结:
1、动态规划的五部曲:
(1)确定动态规划数组dp[]的含义,确定dp[]数组的下标的含义
(2)确定动态规划的递推公式
(3)确定动态规划的初始化条件
(4)确定动态规划的传播方向
(5)举例论证递推公式的正确
2、不同路径Ⅱ(需要重新思考)
要注意障碍所在的多种位置:第一行第一列、起点终点、普通中间网格
3、整数拆分(需要重新思考)
注意对于整数差分获得乘积的最大的几种情况
1、拆分成两个后的乘积就是最大值
2、拆分成两个后,还需要对第二个数进行拆分
遍历的时候,其实将j遍历到i/2即可,因为剩余的已经相当于遍历过了
不同路径
题目链接:62. 不同路径 - 力扣(LeetCode)
代码随想录
整体思路:
从左上到右下:所有的位置(i,j)只能通过向下移动、向右移动,进行到达,所以位置(i.j)与之前状态(i-1,j),(i,j-1)相关,是动态规划的问题。
1、确定动态规划的dp数组含义以及下标含义
dp[i][j]的含义是到达当前位置有多少条路径
i是横坐标,j是纵坐标
2、确定动态规划的递推公式(状态转移方程)
dp[i][j] = dp[i-1][j]+dp[i][j-1]
3、确定动态规划的初始化条件
可以通过图分析可知,在第一行第一列,都是一种路径
4、确定动态规划的方向:
从左向右,从上到下
5、举例论证
简单无需举例
整体代码:
class Solution { public:int uniquePaths(int m, int n) {/*1、确定动态规划的dp数组含义以及下标含义dp[i][j]的含义是到达当前位置有多少条路径i是横坐标,j是纵坐标2、确定动态规划的递推公式(状态转移方程)dp[i][j] = dp[i-1][j]+dp[i][j-1]3、确定动态规划的初始化条件可以通过图分析可知,在第一行第一列,都是一种路径4、确定动态规划的方向左向右,从上到下5、举例论证简单无需举例*/vector<vector<int>>dp(m,vector<int>(n,0));//遍历dpfor(int i=0;i<m;i++){for(int j=0;j<n;j++){if(i==0||j==0){dp[i][j]=1;}else{dp[i][j] = dp[i-1][j] +dp[i][j-1];}}}return dp[m-1][n-1];} };
不同路径Ⅱ
题目链接:63. 不同路径 II - 力扣(LeetCode)
代码随想录
整体思路:
与上一个题类似,需要注意有障碍物
1、障碍物在开始或者终点-->直接返回0
2、障碍物在第一行、第一列,障碍物之后的位置(包含障碍物坐标)路径全部为0
3、障碍物在中间的网格,该点的路径为0
(没进行代码优化,但是结果正确)
整体代码:
class Solution { public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {vector<vector<int>>dp(obstacleGrid.size(),vector<int>(obstacleGrid[0].size(),0));//定义dp数组//初始化//1、注意障碍物在起点、终点的情况if(obstacleGrid[0][0]==1||obstacleGrid[obstacleGrid.size()-1][obstacleGrid[0].size()-1]==1)return 0;//注意第一行、第一列正常应该是一条路径,但是如果出现了障碍,后边的网格的路径为0for(int i=0;i<obstacleGrid.size();i++)//遍历行,所以是第一列{if(obstacleGrid[i][0]==0){dp[i][0] =1;}else if(obstacleGrid[i][0]==1)break;}for(int j=0;j<obstacleGrid[0].size();j++){if(obstacleGrid[0][j]==0){dp[0][j]=1;}else if(obstacleGrid[0][j]==1){break;}}//遍历整个网格,从第二行第二列开始就行,因为第一行第一列初始化了for(int i=1;i<obstacleGrid.size();i++){for(int j=1;j<obstacleGrid[0].size();j++){if(obstacleGrid[i][j]==0)//说明不是障碍物{dp[i][j] = dp[i-1][j]+dp[i][j-1];}else if(obstacleGrid[i][j]==0)//可不写{dp[i][j] = 0;}}}return dp[obstacleGrid.size()-1][obstacleGrid[0].size()-1];//0,1} };
整数拆分
题目链接:343. 整数拆分 - 力扣(LeetCode)
代码随想录
整体思路:
目标:拆分的整数的乘积最大化
dp数组的作用是将当前的元素与之前的状态相联系,
从1遍历到n,相当于拆分,将当前的拆分值与剩余值进行相乘,
n=10
j=1,j*9;对于9还可以继续拆,所以需要找到9的最大的拆分乘积进行记录
j=2,j*8,对于8还可以继续拆,所以需要找到8的最大的查拆乘积分进行记录
。。。
所以,对于一个数进行拆分,需要知道这个数之前的所有数的最大拆分,然后找到最大的值-->当前元素的值与之前元素的状态有关,可以使用动态规划dp
(1)确定dp数组的含义、下标的含义
dp数组表示当前数的最大拆分的乘积,下标表示当前元素
(2)确定dp数组的迭代公式(状态转移方程)
dp[i] = j*dp[i-j];dp[i-j]表示的是dp[i-j]的最大拆分乘积,之所以不需要对j进行拆分是因为j是遍历元素,所有的拆分项都在dp[i-j]中出现过
同时要注意,dp[i]的获取还有一种情况,就是i-j本身就是最大值无需在进行拆分dp[i-j]
dp[i] = max(dp[i],dp[i-j],(i-j)*j),
(3)确定dp数组的初始化
dp[0]=0,dp[1]=1,dp[2]=2;
(4)确定dp数组的方向
从小到n
(5)举例论证
无需举例
整体代码:
class Solution { public:int integerBreak(int n) {//谨记dp数组表示当前元素的最大乘积,下标表示当前数组vector<int>dp(n+1,0);//注意要处理的是n而不是n-1dp[0]=0,dp[1]=1;for(int i=2;i<=n;i++)//遍历所有的dp数组{for(int j=1;j<i;j++)//通过遍历从0-i确定当前的最大dp[i]{//dp状态转移方程dp[i] = max(dp[i],max(j*dp[i-j],(i-j)*j));}}return dp[n];} };
不同的二叉搜索树
题目链接:96. 不同的二叉搜索树 - 力扣(LeetCode)
代码随想录
整体思路:
整体代码: