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

举报
Serendipity·y 发表于 2022/02/16 23:51:01 2022/02/16
【摘要】 一、题目要求 一个机器人位于一个 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

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。