数据结构与算法之深入解析“不同路径”的求解思路与算法示例
Posted Serendipity·y
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法之深入解析“不同路径”的求解思路与算法示例相关的知识,希望对你有一定的参考价值。
一、题目要求
- 一个机器人位于一个 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 * 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];
- 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];
;
② 组合数学
- 从左上角到右下角的过程中,需要移动 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;
;
- 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;
③ 递归
- 由于机器人每次只能向下或者向右移动一步,对于位置 (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)
- 递归的核心逻辑已经写出来,但还少了递归的终止条件,那么什么时候终止呢?就是到达边界时,会触发递归终止,然后返回,终止条件就是当移动到最右边一列、或者最下一行时。
- 也就是当 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);
以上是关于数据结构与算法之深入解析“不同路径”的求解思路与算法示例的主要内容,如果未能解决你的问题,请参考以下文章
数据结构与算法之深入解析“不同路径III”的求解思路与算法示例
数据结构与算法之深入解析“股票的最大利润”的求解思路与算法示例