目录
力扣题号:62. 不同路径 - 力扣(LeetCode)
注:下述题目描述和示例均来自力扣
题目描述
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例
示例 1:
输入:m = 3, n = 7 输出:28
示例 2:
输入:m = 3, n = 2 输出:3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 1. 向右 -> 向下 -> 向下 2. 向下 -> 向下 -> 向右 3. 向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3 输出:28
示例 4:
输入:m = 3, n = 3 输出:6
提示:
1 <= m, n <= 100
- 题目数据保证答案小于等于
2 * 10^9
思路
解法一:动态规划
其实这种路径问题,应该是可以使用DFS或者BFS吧,文章写到这里是我已经写完了动态规划的代码,准备写好思路之后突然灵光乍现,等我吧这里动态规划的思路写完我就去研究一下DFS或者BFS。
好吧我直接回到这里,这里如果使用DFS转化成二叉树来解决,这个时间复杂度拉满了。我直接放弃了,毕竟这种题目还是适合使用动态规划。
先确定一个思路:直行到达一个点,只有一个路径
这里动态规划的思路我们还是根据五步骤来讲解:
(1)dp数组的下标及其含义
这里的dp[i][j] 都不用想肯定是到达当前位置的路径数量了。
(2)确定递推公式
在确定递推公式之前我们先观察一下问题,你会发现,第一行或者第一列的每一个位置都只有一种路径到达,因此你可以选择先把第一行和第一列全部赋值为 1,那么递推公式如下:
也就是到达当前位置只需要左边的点和右边的点的路径的和。
(3)初始化递推数组
在我们这里的话,初始化的内容还有一点多,就是将第一行和第一列都全部初始化为1.
(4)确定遍历顺序
思路都如此明确了,那么这个遍历顺序肯定是,从左到右从上到下了。
(5)根据题意推出dp数组对照
代码实现
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
// 初始化第一行和第一列
for (int i = 0; i < m; i++) {
dp[i][0] = 1;
}
for (int i = 0; i < n; i++) {
dp[0][i] = 1;
}
// 定义y方向的遍历
for (int i = 1; i < m; i++) {
// 定义x方向上的遍历
for (int j = 1; j < n; j++) {
// 这里i,j的点只需要加上左边和上边的路径数
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
for (int i = 0; i < m; i++) {
System.out.println(Arrays.toString(dp[i]));
}
return dp[m - 1][n - 1];
}
}
呃
这咋还写出来了还这么慢。
wdf!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
但是不影响,我有应对之策
解法二:**动态规划优化方法**
这里花费时间最多的在哪里,双层for循环对吧,但是这个核心代码基本优化不了。。。
下一位,我们初始化还有连个for循环,wdf??那第一行要是那么长,我就得初始化那么长,不行,这事我干不了。我不可能如此优化搞鸡毛啊。
我们来想想,这里初始化就是将第一行和第一列进行初始化,也就是进行一个加一操作对吧,那我不初始化了,我在我的dp核心代码(双层for)里面进行+1的操作不可以吗,反正这段代码又优化不了,多加一个1也不影响。
但是这里还是要注意,我们在dp核心里面多加一还不够,因为这只是相当于将一行一列的其中一个进行的初始化的操作,我们需要在最后的结果返回中再+1,这时候就有问题了。为什么不是在dp里面直接+2,那这样就多加了,因为过程是累加的嘛。
递推公式
所以这里的递推公式是有变化的如下所示:
初始化
这里初始化的话,你可以走个形式如下:
有用吗,毫无卵用 ,我们都优化掉了初始化的过程,还初始化干嘛对吧。
具体代码如下(注释及其详细):
class Solution {
public int uniquePaths(int m, int n) {
// 这里的m是网格的高度,n是网格的宽度
// 当前位置的路径数 上面的路径数 左边的路径数
// dp[i][j] = 1 + dp[i-1][j] + dp[i][j-1]
// y x
int[][] dp = new int[m + 1][n + 1];
// 初始化出发点为0路径
dp[1][1] = 0;
// 定义y方向的遍历
for(int i = 1; i <= m; i++){
// 定义x方向上的遍历
for(int j = 1;j <= n;j++){
// 你可以发现第一行也就是dp[1][n]的每个点都只有一种路径
// 同理,第一列也是
// 那么我们就排除掉第一行和第一列,最后加上即可
if(i > 1 && j > 1 ){
// 这里i,j的点只需要加上左边和上边的路径数
// 为什么要+1,因为我们没有计算第一行或者第一列的路径
// 这里当然也可以不加1,那么你就需要将第一行和第一列都赋值为1
// 这样就不需要在这里加一了,当然这里加一还不够
dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + 1;
}
}
}
// 由于将第一行和第一列设置成了0路径,我们在过程中只加了行或者列其中一个的1
// 所以在最后的结果中需要加上另一个的1就是我们最终的答案了
// 这样写可能不便于理解,但是少去了赋值的过程
// 在较大的数据量的情况下应该是更快的
return dp[m][n] + 1;
}
}
那这还说啥了,easy!!!
解法三:数学
看了一下力扣的题解,发现还可以使用数学方法,然后我细想了一下确实是可以使用排列组合来解决这个问题的。
具体思路就是,一个矩形从左上角走到右下角,无论是哪一条路径,都需要 步才可以到达。其中向下走 步, 向右走 步, 那么就可以看做一个排列组合中的问题了(当然这里只有组合),我们需要在 中选择 向下走 步的方案或者 向右走 步的方案,其实答案都是一样的。
所以我们的公式大概就是如下的样子:
或者是
class Solution {
public int uniquePaths(int m, int n) {
long res = 1;
for (int x = n, y = 1; y <= m - 1; x++, y++) {
res = res * x / y;
}
return (int) res;
}
}
那这也没超过我的优化版动态规划的速度和内存占用啊,就这
总结
这里就涉及到了二维的动态规划了,但是这里仍然不算是太难,对于初学者来说,并且是看完了我动态规划前两篇文章并且理解的同学来说,这道题应该是可以直接写出来的,或许想不到优化的方法,但是常规的方法应该还是没有问题的。
总之,多学多练,勤能补拙加油呀ヾ(◍°∇°◍)ノ゙