今日总结:
1、动态规划的五部曲:
(1)确定动态规划数组dp[]的含义,确定dp[]数组的下标的含义
(2)确定动态规划的递推公式
(3)确定动态规划的初始化条件
(4)确定动态规划的传播方向
(5)举例论证递推公式的正确
2、不同路径Ⅱ(需要重新思考)
要注意障碍所在的多种位置:第一行第一列、起点终点、普通中间网格
3、整数拆分(需要重新思考)
注意对于整数差分获得乘积的最大的几种情况
1、拆分成两个后的乘积就是最大值
2、拆分成两个后,还需要对第二个数进行拆分
遍历的时候,其实将j遍历到i/2即可,因为剩余的已经相当于遍历过了
不同路径
整体思路:
从左上到右下:所有的位置(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、举例论证
简单无需举例
整体代码:
cppclass 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)); //遍历dp for(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
(没进行代码优化,但是结果正确)
整体代码:
cppclass 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; //注意第一行、第一列正常应该是一条路径,但是如果出现了障碍,后边的网格的路径为0 for(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 } };
整数拆分
整体思路:
目标:拆分的整数的乘积最大化
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)举例论证
无需举例
整体代码:
cppclass Solution { public: int integerBreak(int n) { //谨记dp数组表示当前元素的最大乘积,下标表示当前数组 vector<int>dp(n+1,0);//注意要处理的是n而不是n-1 dp[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)
整体思路:
整体代码: