当前位置:   article > 正文

代码随想录算法训练营 day39 |62.不同路径、63. 不同路径 II

代码随想录算法训练营 day39 |62.不同路径、63. 不同路径 II

目录

一、(leetcode 62)不同路径

1.动态规划

1)确定dp数组(dp table)以及下标的含义

2)确定递推公式

3)dp数组的初始化

4)确定遍历顺序

5)举例推导dp数组

 2.数论方法

二、(leetcode 63)不同路径 II


一、(leetcode 62)不同路径

力扣题目链接

1.动态规划

机器人从(0 , 0) 位置出发,到(m - 1, n - 1)终点。

按照动规五部曲来分析:

1)确定dp数组(dp table)以及下标的含义

dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

2)确定递推公式

想要求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]只有这两个方向过来。

3)dp数组的初始化

首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。

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

4)确定遍历顺序

这里要看一下递推公式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]一定是有数值的。

5)举例推导dp数组

如图所示:

62.不同路径1

以上动规五部曲分析完毕,C++代码如下:

  1. class Solution {
  2. public:
  3. int uniquePaths(int m, int n) {
  4. vector<vector<int>> dp(m, vector<int>(n, 0));
  5. for (int i = 0; i < m; i++) dp[i][0] = 1;
  6. for (int j = 0; j < n; j++) dp[0][j] = 1;
  7. for (int i = 1; i < m; i++) {
  8. for (int j = 1; j < n; j++) {
  9. dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
  10. }
  11. }
  12. return dp[m - 1][n - 1];
  13. }
  14. };
  • 时间复杂度:O(m × n)
  • 空间复杂度:O(m × n)

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

  1. class Solution {
  2. public:
  3. int uniquePaths(int m, int n) {
  4. vector<int> dp(n);
  5. for (int i = 0; i < n; i++) dp[i] = 1;
  6. for (int j = 1; j < m; j++) {
  7. for (int i = 1; i < n; i++) {
  8. dp[i] += dp[i - 1];
  9. }
  10. }
  11. return dp[n - 1];
  12. }
  13. };
  • 时间复杂度:O(m × n)
  • 空间复杂度:O(n)

 2.数论方法

在这个图中,可以看出一共m,n的话,无论怎么走,走到终点都需要 m + n - 2 步。

62.不同路径

在这m + n - 2 步中,一定有 m - 1 步是要向下走的,不用管什么时候向下走。

那么有几种走法呢? 可以转化为,给你m + n - 2个不同的数,随便取m - 1个数,有几种取法。

那么这就是一个组合问题了。

62.不同路径2

求组合的时候,要防止两个int相乘溢出! 所以不能把算式的分子都算出来,分母都算出来再做除法。

例如如下代码是不行的。

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

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

  1. class Solution {
  2. public:
  3. int uniquePaths(int m, int n) {
  4. long long numerator = 1; // 分子
  5. int denominator = m - 1; // 分母
  6. int count = m - 1;
  7. int t = m + n - 2;
  8. while (count--) {
  9. numerator *= (t--);
  10. while (denominator != 0 && numerator % denominator == 0) {
  11. numerator /= denominator;
  12. denominator--;
  13. }
  14. }
  15. return numerator;
  16. }
  17. };
  • 时间复杂度:O(m)
  • 空间复杂度:O(1)

二、(leetcode 63)不同路径 II

力扣题目链接

有障碍的话,其实就是标记对应的dp table(dp数组)保持初始值(0)就可以了

动规五部曲:

1)确定dp数组(dp table)以及下标的含义

dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

2)确定递推公式

递推公式和62.不同路径一样,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。

但需要注意一点,因为有了障碍,(i, j)如果就是障碍的话应该就保持初始状态(初始状态为0)

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

3)dp数组如何初始化

62.不同路径 (opens new window)不同路径中我们给出如下的初始化:

  1. vector<vector<int>> dp(m, vector<int>(n, 0)); // 初始值为0
  2. for (int i = 0; i < m; i++) dp[i][0] = 1;
  3. 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。

如图:

63.不同路径II

下标(0, j)的初始化情况同理。

所以本题初始化代码为:

  1. vector<vector<int>> dp(m, vector<int>(n, 0));
  2. for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
  3. 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]同理

4)确定遍历顺序

从递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 中可以看出,一定是从左到右一层一层遍历,这样保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值。

  1. for (int i = 1; i < m; i++) {
  2. for (int j = 1; j < n; j++) {
  3. if (obstacleGrid[i][j] == 1) continue;
  4. dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
  5. }
  6. }

5)举例推导dp数组

拿示例1来举例如题:

63.不同路径II1

对应的dp table 如图:

63.不同路径II2

如果这个图看不懂,建议再理解一下递归公式,然后照着文章中说的遍历顺序,自己推导一下!

动规五部分分析完毕,对应C++代码如下:

  1. class Solution {
  2. public:
  3. int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
  4. int m = obstacleGrid.size();
  5. int n = obstacleGrid[0].size();
  6. if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1) //如果在起点或终点出现了障碍,直接返回0
  7. return 0;
  8. vector<vector<int>> dp(m, vector<int>(n, 0));
  9. for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1;
  10. for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1;
  11. for (int i = 1; i < m; i++) {
  12. for (int j = 1; j < n; j++) {
  13. if (obstacleGrid[i][j] == 1) continue;
  14. dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
  15. }
  16. }
  17. return dp[m - 1][n - 1];
  18. }
  19. };
  • 时间复杂度:O(n × m),n、m 分别为obstacleGrid 长度和宽度
  • 空间复杂度:O(n × m)

同样给出空间优化版本:

  1. class Solution {
  2. public:
  3. int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
  4. if (obstacleGrid[0][0] == 1)
  5. return 0;
  6. vector<int> dp(obstacleGrid[0].size());
  7. for (int j = 0; j < dp.size(); ++j)
  8. if (obstacleGrid[0][j] == 1)
  9. dp[j] = 0;
  10. else if (j == 0)
  11. dp[j] = 1;
  12. else
  13. dp[j] = dp[j-1];
  14. for (int i = 1; i < obstacleGrid.size(); ++i)
  15. for (int j = 0; j < dp.size(); ++j){
  16. if (obstacleGrid[i][j] == 1)
  17. dp[j] = 0;
  18. else if (j != 0)
  19. dp[j] = dp[j] + dp[j-1];
  20. }
  21. return dp.back();
  22. }
  23. };
  • 时间复杂度:O(n × m),n、m 分别为obstacleGrid 长度和宽度
  • 空间复杂度:O(m)
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/211995
推荐阅读
相关标签
  

闽ICP备14008679号