数据结构与算法之深入解析“不同路径”的求解思路与算法示例

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”的求解思路与算法示例

数据结构与算法之深入解析“完美数”的求解思路与算法示例

数据结构与算法之深入解析“股票的最大利润”的求解思路与算法示例

数据结构与算法之深入解析“安装栅栏”的求解思路与算法示例

数据结构与算法之深入解析“最长连续序列”的求解思路与算法示例

数据结构与算法之深入解析“路径总和”的求解思路与算法示例