路线
62.不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7 输出:28
示例 2: 输入:m = 2, n = 3 输出:3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。
- 向右 -> 向右 -> 向下
- 向右 -> 向下 -> 向右
- 向下 -> 向右 -> 向右
示例 3: 输入:m = 7, n = 3 输出:28
示例 4: 输入:m = 3, n = 3 输出:6
提示:
- 1 <= m, n <= 100
- 题目数据保证答案小于等于 2 * 10^9
思路
这是一题经典的动态规划,也展现动态规划最基本的思路,即状态转移
第一步 确定dp[i][j]的含义
dp[i][j]表示到达坐标(i,j)最多有几条线路
第二步 确定dp递推公式
我们看倒数第二个位置都来一定自于上方(i-1,j)和左方(i,j-1),所以我们可以假设 d[i][j]=d[i-1][j]+d[i][j-1]
第三步 初始化
但是我们就会发现dp[0][j]这一行和的d[i][0] 也就是最左边和上边是没有前项的,所以我们需要给出初始化值,由dp的含义我们可以知道,第一行的值都为1,因为只有从左向右这一条路,第一列同理
第四步 确定遍历顺序
因为起点已经决定好了,所以我们的遍历方向已经决定是从左向右,从上至下
第五步 试写dp
成功!
代码实现
class Solution {
public int uniquePathsWithObstacles(int m, int n) {
int dp[][]=new int[m+1][n+1];
for(int i=0;i<m;i++)
dp[i][0]=0;
for(int i=0;i<n;i++)
dp[0][i]=0;
for(int i=1;i<m;i++)
for(int j=1;j<n;j++)
dp[i][j]=dp[i-1][j]+dp[i][j-1];
return dp[m-1][n-1];
}
}
62.不同路径II
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。
示例 1:
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]] 输出:2 解释: 3x3 网格的正中间有一个障碍物。 从左上角到右下角一共有 2 条不同的路径:
- 向右 -> 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右 -> 向右
示例 2:
输入:obstacleGrid = [[0,1],[0,0]] 输出:1
提示:
- m == obstacleGrid.length
- n == obstacleGrid[i].length
- 1 <= m, n <= 100
- obstacleGrid[i][j] 为 0 或 1
思路
这是一题经典的动态规划,也展现动态规划最基本的思路,即状态转移,比上面那道题目而言,多了一个条件,加入了障碍物。但实际也很好解决
第一步 确定dp[i][j]的含义
dp[i][j]表示到达坐标(i,j)最多有几条线路
第二步 确定dp递推公式
如果(i,j)不是障碍物 我们看倒数第二个位置都来一定自于上方(i-1,j)和左方(i,j-1),所以我们可以假设 d[i][j]=d[i-1][j]+d[i][j-1] 如果是障碍物 d[i][j]=0;
第三步 初始化
但是我们就会发现dp[0][j]这一行和的d[i][0] 也就是最左边和上边是没有前项的,所以我们需要给出初始化值,由dp的含义我们可以知道,第一行的值都为1,因为只有从左向右这一条路,第一列同理,但是有了障碍物,则之后的道路都不通了 所以 for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
第四步 确定遍历顺序
因为起点已经决定好了,所以我们的遍历方向已经决定是从左向右,从上至下
第五步 试写dp
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m=obstacleGrid.length;
int n=obstacleGrid[0].length;
int dp[][]=new int[m+1][n+1];
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;
for(int i=1;i<m;i++)
{for(int j=1;j<n;j++)
{
if(obstacleGrid[i][j]==1)
continue;
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
}
整数拆分
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1: 输入: 2 输出: 1
\解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2: 输入: 10 输出: 36 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 说明: 你可以假设 n 不小于 2 且不大于 58。
思路
、
第一步 确定dp[i][j]的含义
dp[i]表示i最多的能拆分出的正整数和;
第二步 确定dp递推公式
我们回到无穷远处,可以得知,dp[i]的值有两种可能,两位数相乘: (i-j)*j 或者是两位以上相乘 dp[i-j]*j 所以dp[i]=max(dp[i-j]*j,(i-j)*j) 但是这么做还是有问题,就是算出的值不一定是一次里最大的值 所以dp[i]=max(dp[i],dp[i-j]*j,(i-j)*j)
第三步 初始化
dp[2]=1
第四步 确定遍历顺序
因为起点已经决定好了,所以我们的遍历方向已经决定是从左向右,从上至下
第五步 试写dp
class Solution {
public int integerBreak(int n) {
int dp[]=new int[n+1];
dp[2]=1;
for(int i=2;i<=n;i++)
for(int j=0;j<i;j++)
dp[i]=Math.max(dp[i],Math.max(dp[i-j]*j,(i-j)*j));
return dp[n];
}
}
不同的二叉搜索树
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
示例:
思路
第一步确定dp[i]定义
dp[i],n=i时,共有多少种二叉搜索树
第二步确定递推公式
dp[i]=dp[i的左子树]形态*右子树形态,什么意思呢?我们可以看下递推公式
dp[0]=1;
dp[1]=1;
dp[2]=2
dp[3]=dp[2]+dp[0]+dp[1]+dp[1]
dp[4]=dp[3]*dp[0]+dp[2]*dp[1]+dp[1]*dp[2]
所以dp[i]+=dp[i-j]*d[j-1]
第三步初始化
由递推公式可以得出,dp[i]依赖于i-j,所以dp[0]是初始项,所以dp[0]为0个节点的搜索树,0个节点只有一钟搜索树 所以dp[0]=1;
第四步确定遍历顺序
由前开始所以,i>j>1,i从1开始;j从1开始
第五步
上文已经写过dp数组了
代码实现
class Solution {
public int numTrees(int n) {
int dp[]=new int[n];
dp[0]=1;
if(n==0)
return dp[0];
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
dp[i]+=dp[i-j]*dp[j];
return dp[n];
}
}