62. Unique Paths(不同路径)两种解法(C++ & 注释)

1. 题目描述

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
在这里插入图片描述
问总共有多少条不同的路径?

例如,上图是一个7 x 3 的网格。有多少可能的路径?

示例 1:

输入: m = 3, n = 2
输出: 3
解释: 从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向右 -> 向下
  2. 向右 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向右

示例 2:

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

提示:

  • 1 <= m, n <= 100
  • 题目数据保证答案小于等于 2 * 10 ^ 9

题目链接:中文题目英文题目

2. 回溯法(Backtracking, Time Limit Exceeded)

2.1 解题思路

因为机器人只能向右和向下走,那么:

  1. 右走一步:j + 1,或者下走一步:i + 1;如果j越界,则返回上一步,继续i + 1,;如果i越界,则返回上一步;
  2. 如果i = 终点i且j = 终点j,则路径数加一,返回上一步继续1. 的步骤;

回溯法的思路简单清晰,但是有非常多重复步骤(前面有*),如下图所示:
在这里插入图片描述
这种重复计算导致大量数据处理超时,所以接下来的动态规划方法中,我们需要对其进行简化。

2.2 实例代码

2.2.1 不使用面向对象(推荐)

class Solution {
    int count = 0;

    void move(int i, int j, int iFinish, int jFinish) {
        if (i >= iFinish + 1 || j >= jFinish + 1) return;
        if (i == iFinish  && j == jFinish) { count++; return; }
        move(i, j + 1, iFinish, jFinish);
        move(i + 1, j, iFinish, jFinish);
    }
public:
    int uniquePaths(int m, int n) {
        move(0, 0, m - 1, n - 1);
        return this->count;
    }
};

2.2.2 使用面向对象

struct Point {
    int x;
    int y;
    Point(int i, int j):x(i), y(j) {}
    bool operator==(Point& p) { return this->x == p.x && this->y == p.y; }
};

class Solution {
    int count = 0;

    void move(Point& start, Point& finish) {
        if (start.x >= finish.x + 1 || start.y >= finish.y + 1) return;
        if (start == finish) { count++; return; }
        start.x++;
        move(start, finish);
        start.x--;
        start.y++;
        move(start, finish);
        start.y--;
    }
public:
    int uniquePaths(int m, int n) {
        Point finish(n - 1, m - 1), start(0, 0);
        move(start, finish);
        return this->count;
    }
};

3. 动态规划(Dynamic Programming)

3.1 解题思路

根据2. 回溯法(Backtracking)的分析,我们发现某一点坐标(i, j)一共有多少个路径,取决于它右边的坐标(i, j+1)和下面的坐标(i+1, j)的路径数,即:

dp[i][j] = dp[i + 1][j] + dp[i][j + 1]

所以我们可以用一个二维数组来存储上面重复的步骤,如果已经处理过相同的情况,直接返回存储的数值,避免重复计算 ,适用代码:3.2.1 递归(Recursion)。

既然已经使用了递归,那么能不能使用迭代来解决呢?首先,我们来观察一下每个坐标的路径数,m = n = 3:

---
631
321
111

通过上图,我们发现:右边界(j = n - 1)和下边界(i = m - 1)的路径数总是1,因为右边界的值只取决于它的下坐标值,不能往右走,而下边界的值只取决于它的右坐标值,不能往下走。而其余坐标的值和上面总结出来的公式是一致的,所以我们只需先初始化右边界和下边界的值为1,然后用上面的公式计算中间的坐标的值,最后返回dp[0][0]即可,适用代码:3.2.2 迭代(Iteration)。

最后,我们换一种观察角度,从最大行(i),最大列(j)开始观察,我们发现前一行等于后一行中,本列的值 += 后一列的值,即:

dp[j] = dp[j] + dp[j + 1]

这样就可以把之前使用的二维数组更改成一位数组,以便达到节约空间的效果。如果有童鞋还是不是很能理解上面的思路,可以这样理解:

在这里插入图片描述

原来我们是把右坐标值和下坐标值分别存储到对应坐标空间里面(dp[i + 1][j] 和 dp[i][j + 1]),而现在我们把上面两个坐标的值放分别在两个连续的空间中(dp[j] + dp[j + 1]),然后相加以后把新值放在下坐标的空间中(dp[j]),适用代码:3.2.3 滚动优化版迭代(Iteration updated by row)。

3.2 实例代码

3.2.1 递归(Recursion)

class Solution {
    int move(int i, int j, int iFinish, int jFinish, vector<vector<int>>& memory) {
        if (i >= iFinish + 1 || j >= jFinish + 1) return 0;
        if (memory[i][j]) return memory[i][j];
        if (i == iFinish  && j == jFinish) return 1;
        memory[i][j] = move(i, j + 1, iFinish, jFinish, memory) +  move(i + 1, j, iFinish, jFinish, memory);
        return memory[i][j];
    }
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> memory(m, vector<int>(n, 0));
        memory[0][0] = move(0, 0, m - 1, n - 1, memory); // corner case: m = n = 1
        return memory[0][0];
    }
};

3.2.2 迭代(Iteration)

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> memory(m, vector<int>(n, 1)); // 这里已经初始化了右边界和下边界
        for (int i = m - 2; i >= 0; i--)
            for (int j = n - 2; j >= 0; j--)
                memory[i][j] = memory[i + 1][j] + memory[i][j + 1];

        return memory[0][0];
    }
};

3.2.3 滚动优化版迭代(Iteration updated by row)

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

4. 参考资料

  1. LeetCode - 62. Unique Paths(不同的路径数量)(简单dp)

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值