【数据结构与算法】之深入解析“不同路径”的求解思路与算法示例
【摘要】
一、题目要求
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” ),机器人每次只能向下或者向右移动一步,机器人试图达到网格的右下角(在下图中标记为 “Finish” ...
一、题目要求
- 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” ),机器人每次只能向下或者向右移动一步,机器人试图达到网格的右下角(在下图中标记为 “Finish” ),问总共有多少条不同的路径?
- 示例 1:
输入:m = 3, n = 7
输出:28
- 1
- 2
- 示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 示例 3:
输入:m = 7, n = 3
输出:28
- 1
- 2
- 示例 4:
输入:m = 3, n = 3
输出:6
- 1
- 2
- 提示:
-
- 1 <= m, n <= 100;
-
- 题目数据保证答案小于等于 2 * 109。
二、求解算法
① 动态规划
- 用 f(i,j) 表示从左上角走到 (i,j) 的路径数量,其中 i 和 j 的范围分别是 [0,m) 和 [0,n)。由于每一步只能从向下或者向右移动一步,因此要想走到 (i,j),如果向下走一步,那么会从 (i−1,j) 走过来;如果向右走一步,那么会从 (i,j−1) 走过来。
- f[i,j] 表示从 (0,0) 走到 (i,j) 的所有不同路径的方案数,那么,f[m-1][n-1] 就表示从网格左上角到网格右下角的所有不同路径的方案数,即为答案:
- 由于限制了只能向下走或者向右走,因此到达 (i,j) 有两条路径:
-
- 从上方转移过来,f[i][j] = f[i-1][j];
-
- 从左方转移过来,f[i][j] = f[i][j-1];
- 因此可以写出动态规划转移方程,将向右和向下两条路径的方案数相加起来:
- 需要注意的是,如果 i=0,那么 f(i−1,j) 并不是一个满足要求的状态,需要忽略这一项;同理,如果 j=0,那么 f(i,j−1) 并不是一个满足要求的状态,也需要忽略这一项。
- 初始条件为 f(0,0)=1,即从左上角走到左上角有一种方法,从 (0,0) 到达 (0,0) 只有一条路径:
- 最终的答案即为 f(m−1,n−1)。
- 为了方便代码编写,可以将所有的 f(0,j) 以及 f(i,0) 都设置为边界条件,它们的值均为 1。
- Java 示例:
class Solution {
public int uniquePaths(int m, int n) {
int[][] f = new int[m][n];
for (int i = 0; i < m; ++i) {
f[i][0] = 1;
}
for (int j = 0; j < n; ++j) {
f[0][j] = 1;
}
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
return f[m - 1][n - 1];
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- C++ 示例:
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> f(m, vector<int>(n));
for (int i = 0; i < m; ++i) {
f[i][0] = 1;
}
for (int j = 0; j < n; ++j) {
f[0][j] = 1;
}
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
return f[m - 1][n - 1];
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
② 组合数学
- 从左上角到右下角的过程中,需要移动 m+n−2 次,其中有 m−1 次向下移动,n−1 次向右移动,因此路径的总数,就等于从 m+n−2 次移动中选择 m−1 次向下移动的方案数,即组合数:
- 因此直接计算出这个组合数即可,计算的方法有很多种:
-
- 如果使用的语言有组合数计算的 API,可以调用 API 计算;
-
- 如果没有相应的 API,可以使用如下方式计算:
- C++ 示例:
class Solution {
public:
int uniquePaths(int m, int n) {
long long ans = 1;
for (int x = n, y = 1; y < m; ++x, ++y) {
ans = ans * x / y;
}
return ans;
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- Java 示例:
class Solution {
public int uniquePaths(int m, int n) {
long ans = 1;
for (int x = n, y = 1; y < m; ++x, ++y) {
ans = ans * x / y;
}
return (int) ans;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
③ 递归
- 由于机器人每次只能向下或者向右移动一步,对于位置 (i,j) 来说,如果要再次移动,只有往右走、或者往下走:
-
- 往右:i 不动,j+1;
-
- 往下:j+1,i 不动;
- 那么从 (0,0) 出发,走到 (m,n) 的所有路径,应该是由两条路线加起来的:
-
- 从 (0,0) 为起点,往右的所有路径;
-
- 从 (0,0) 为起点,往下的所有路径。
- 把上面的加起来即为总路径,因此递归的核心逻辑就是:
result = dfs(i + 1, j) + dfs(i, j + 1)
- 1
- 递归的核心逻辑已经写出来,但还少了递归的终止条件,那么什么时候终止呢?就是到达边界时,会触发递归终止,然后返回,终止条件就是当移动到最右边一列、或者最下一行时。
- 也就是当 i == m - 1 时,或者 j == n - 1 时,递归返回:
- 到达边界时,返回 1 即可,如果是位于第一列,那么无论怎么往下,都只有一条路可以走;同理如果位于第一行,往右也只有一条路可以走。
- 当然这题用纯用递归是不行的,因为有大量的重复调用会导致超时,如下图所示,起点为 (0,0) 时,会有大量重复调用:
- Java 示例:
class Solution {
public int uniquePaths(int m, int n) {
return dfs(new HashMap<Pair,Integer>(), 0, 0, m, n);
}
private int dfs(Map<Pair,Integer> cache, int i, int j, int m, int n) {
Pair p = new Pair(i,j);
// 如果(i,j)在缓存中则直接返回
if(cache.containsKey(p)) {
return cache.get(p);
}
// 到达边界时,返回 1
if(i == m - 1 || j == n - 1) {
return 1;
}
// 继续递归调用,往下i+1,往右j+1
cache.put(p, dfs(cache, i + 1, j, m, n) + dfs(cache, i, j + 1, m, n) );
return cache.get(p);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
文章来源: blog.csdn.net,作者:Serendipity·y,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/Forever_wj/article/details/122847761
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)