代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II

代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II

  • 62.不同路径
    • 深搜
    • 动态规划
    • 数论方法
    • :eyes:题目总结:eyes:
  • 63. 不同路径 II
    • :eyes:题目总结:eyes:

62.不同路径

题目链接
视频讲解
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” ),机器人每次只能向下或者向右移动一步,机器人试图达到网格的右下角(在下图中标记为 “Finish” ),问总共有多少条不同的路径?
代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II_第1张图片

输入:m = 3, n = 7
输出:28

深搜

这道题目,刚一看最直观的想法就是用图论里的深搜,来枚举出来有多少种路径,注意题目中说机器人每次只能向下或者向右移动一步,那么其实机器人走过的路径可以抽象为一棵二叉树,而叶子节点就是终点!
如图举例:
此时问题就可以转化为求二叉树叶子节点的个数,代码如下:

class Solution {
private:
    int dfs(int i, int j, int m, int n) {
        if (i > m || j > n) return 0; // 越界了
        if (i == m && j == n) return 1; // 找到一种方法,相当于找到了叶子节点
        return dfs(i + 1, j, m, n) + dfs(i, j + 1, m, n);
    }
public:
    int uniquePaths(int m, int n) {
        return dfs(1, 1, m, n);
    }
};

动态规划

机器人从(0 , 0) 位置出发,到(m - 1, n - 1)终点,按照动规五部曲来分析:
确定dp数组(dp table)以及下标的含义,dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径,确定递推公式,想要求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1,此时在回顾一下 dp[i - 1][j] 表示啥,是从(0, 0)的位置到(i - 1, j)有几条路径,dp[i][j - 1]同理,那么很自然,dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来,dp数组的初始化,如何初始化呢,首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理
所以初始化代码为:

for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;

确定遍历顺序,这里要看一下递推公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了,这样就可以保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值的,举例推导dp数组
如图所示:
代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II_第2张图片

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m, vector<int>(n, 0));
        for (int i = 0; i < m; i++) dp[i][0] = 1;
        for (int j = 0; j < n; j++) dp[0][j] = 1;
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
};

其实用一个一维数组(也可以理解是滚动数组)就可以了,但是不利于理解,可以优化点空间,建议先理解了二维,在理解一维,C++代码如下:

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<int> dp(n);
        for (int i = 0; i < n; i++) dp[i] = 1;
        for (int j = 1; j < m; j++) {
            for (int i = 1; i < n; i++) {
                dp[i] += dp[i - 1];
            }
        }
        return dp[n - 1];
    }
};

数论方法

在这个图中,可以看出一共m,n的话,无论怎么走,走到终点都需要 m + n - 2 步
代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II_第3张图片
在这m + n - 2 步中,一定有 m - 1 步是要向下走的,不用管什么时候向下走,那么有几种走法呢? 可以转化为,给你m + n - 2个不同的数,随便取m - 1个数,有几种取法,那么这就是一个组合问题了
那么答案,如图所示:
代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II_第4张图片
求组合的时候,要防止两个int相乘溢出! 所以不能把算式的分子都算出来,分母都算出来再做除,例如如下代码是不行的

class Solution {
public:
    int uniquePaths(int m, int n) {
        int numerator = 1, denominator = 1;
        int count = m - 1;
        int t = m + n - 2;
        while (count--) numerator *= (t--); // 计算分子,此时分子就会溢出
        for (int i = 1; i <= m - 1; i++) denominator *= i; // 计算分母
        return numerator / denominator;
    }
};

需要在计算分子的时候,不断除以分母,代码如下:

class Solution {
public:
    int uniquePaths(int m, int n) {
        long long numerator = 1; // 分子
        int denominator = m - 1; // 分母
        int count = m - 1;
        int t = m + n - 2;
        while (count--) {
            numerator *= (t--);
            while (denominator != 0 && numerator % denominator == 0) {
                numerator /= denominator;
                denominator--;
            }
        }
        return numerator;
    }
};

题目总结

本文分别给出了深搜,动规,数论三种方法,深搜当然是超时了,然后在给出动规的方法,依然是使用动规五部曲,这次我们就要考虑如何正确的初始化了,初始化和遍历顺序其实也很重要

63. 不同路径 II

题目链接
视频讲解
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” ),机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”),现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 1 和 0 来表示
代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II_第5张图片

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2

这道题相对于62.不同路径就是有了障碍,第一次接触这种题目的同学可能会有点懵,这有障碍了,应该怎么算呢?62.不同路径中我们已经详细分析了没有障碍的情况,有障碍的话,其实就是标记对应的dp table(dp数组)保持初始值(0)就可以了,
动规五部曲:
确定dp数组(dp table)以及下标的含义,dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径,确定递推公式,递推公式和62.不同路径一样,dp[i][j] = dp[i - 1][j] + dp[i][j - 1],但这里需要注意一点,因为有了障碍,(i, j)如果就是障碍的话应该就保持初始状态(初始状态为0)
所以代码为:

if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j]
    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}

dp数组如何初始化,在62.不同路径不同路径中我们给出如下的初始化:

vector<vector<int>> dp(m, vector<int>(n, 0)); // 初始值为0
for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;

因为从(0, 0)的位置到(i, 0)的路径只有一条,所以dp[i][0]一定为1,dp[0][j]也同理,但如果(i, 0) 这条边有了障碍之后,障碍之后(包括障碍)都是走不到的位置了,所以障碍之后的dp[i][0]应该还是初始值0
如图:
代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II_第6张图片
下标(0, j)的初始化情况同理,所以本题初始化代码为:

vector<vector<int>> dp(m, vector<int>(n, 0));
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;

注意代码里for循环的终止条件,一旦遇到obstacleGrid[i][0] == 1的情况就停止dp[i][0]的赋值1的操作,dp[0][j]同理,确定遍历顺序,从递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 中可以看出,一定是从左到右一层一层遍历,这样保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值
代码如下:

for (int i = 1; i < m; i++) {
    for (int j = 1; j < n; j++) {
        if (obstacleGrid[i][j] == 1) continue;
        dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
    }
}

举例推导dp数组,拿示例1来举例如题:
代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II_第7张图片
对应的dp table 如图:
代码随想录算法训练营第三十九天 | 62.不同路径,63. 不同路径 II_第8张图片
如果这个图看不懂,建议再理解一下递归公式,然后照着文章中说的遍历顺序,自己推导一下!动规五部分分析完毕,对应C++代码如下:

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
	if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1) //如果在起点或终点出现了障碍,直接返回0
            return 0;
        vector<vector<int>> dp(m, vector<int>(n, 0));
        for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
        for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (obstacleGrid[i][j] == 1) continue;
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
};

题目总结

本题是62.不同路径的障碍版,整体思路大体一致,但就算是做过62.不同路径,在做本题也会有感觉遇到障碍无从下手,其实只要考虑到,遇到障碍dp[i][j]保持0就可以了

你可能感兴趣的:(算法,数据结构)