「动态规划」超经典的路径问题,你会做了吗?

62. 不同路径icon-default.png?t=N7T8https://leetcode.cn/problems/unique-paths/description/一个机器人位于一个m x n网格的左上角(起始点在下图标记为"Start")。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为"Finish")。问总共有多少条不同的路径?

  1. 输入:m = 3,n = 7,输出:28。
  2. 输入:m = 3,n = 2,输出:3,解释:从左上角开始,总共有3条路经可以到达右下角,向右 -> 向下 -> 向下;向下 -> 向下 -> 向右;向下 -> 向右 -> 向下。
  3. 输入:m = 7,n = 3,输出:28。
  4. 输入:m = 3,n = 3,输出:6。

提示:1 <= m,n <= 100;题目数据保证答案小于等于2 x 10^9。


我们用动态规划的思想来解决这个问题。

确定状态表示:根据经验和题目要求,我们用dp[i][j]表示走到[i, j]位置处,一共有多少种方式

推导状态转移方程:只有2种情况能到达[i, j]位置。

  • 先从左上角到达[i - 1, j],再向下走一步到达[i, j]。
  • 先从左上角到达[i, j - 1],再向右走一步到达[i, j]。

所以,从左上角到达[i, j]位置的方法数,就等于从左上角到达[i - 1, j]位置的方法数加上从左上角到达[i, j - 1]位置的方法数,也就是说,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

初始化:根据状态转移方程,我们在计算dp表的最上面一行和最左边一列时会越界。我们很容易想到,从网格的左上角出发,到达网格的最上面一行和最左边一列的任意一个位置,方法是唯一的。所以,我们只需要把dp表的最上面一行和最左边一列都初始化为1即可。

填表顺序:由于dp[i][j]依赖于dp[i - 1][j]和dp[i][j - 1],所以应从上往下,从左往右填表

返回值:应返回dp表右下角的值,即dp[m - 1][n - 1],表示从网格左上角到达右下角的方法数。

细节问题:由于dp表的规模和网格的规模一样大,所以dp表的规模为m x n

class Solution {
public:
    int uniquePaths(int m, int n) {
        // 创建dp表
        vector<vector<int>> dp(m, vector<int>(n, 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];
    }
};

63. 不同路径 IIicon-default.png?t=N7T8https://leetcode.cn/problems/unique-paths-ii/description/现在考虑网格中有障碍物,那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用1和0表示。

  1. 输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]],输出:2,解释:3x3网格的正中间有一个障碍物,从左上角到右下角一共有2条不同的路径:向右 -> 向右 -> 向下 -> 向下;向下 -> 向下 -> 向右 -> 向右。
  2.  输入:obstacleGrid = [[0,1],[0,0]],输出:1。

提示:m == obstacleGrid.length,n == obstacleGrid[i].length;1 <= m,n <= 100;obstacleGrid[i][j]为0或1。


依然用动态规划的思想来解决这个问题。状态表示和上题相同。

推导状态转移方程:只有2种情况能到达[i, j]位置。

  • 先从左上角到达[i - 1, j],再向下走一步到达[i, j]。
  • 先从左上角到达[i, j - 1],再向右走一步到达[i, j]。

所以状态转移方程依然是dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。但是如果这个位置有障碍物,也就是obstacleGrid[i][j] == 1,那么就无法到达[i, j]位置,dp[i][j] = 0

初始化:依然需要初始化最上面一行和最左边一列。这里我们用添加辅助结点的方法,在dp表的最上面和最左边分别加上一行一列。由于新增的辅助结点要保证后续的填表是正确的,所以要认真推导一下,填什么值比较合适。先考虑最简单的情况,网格的最上面一行和最左边一列都没有障碍物,我们把dp表画出来:

* * * *
* ? ? ?
* ?

*位置就是辅助结点。如果把*位置全部填0可不可以呢?显然不行,因为如果这么填,接下来根据状态转移方程,?位置也会全部填0,这是不对的。如果没有障碍物,那么?位置应该全部填1,如何保证?位置全部填1呢?很简单,把其中一个*填1,其他*都填0就行了,就像这样:

0 1 0 0
0 ? ? ?
0 ?

还没看明白的朋友们可以根据状态转移方程填一下?位置的值,你会发现此时?位置的值都是1,符合预期。接下来的问题是,如果有障碍物,会影响结果的正确性吗?答案是:不会。我们假设其中一个?位置是障碍物,那么根据状态转移方程,这个位置就会填0,和辅助结点无关。比如,我们把其中一个?位置填了0:

0 1 0 0
0 ? 0 #
0 ?

注意最右边那个#,根据状态转移方程,这个位置最终也会填0,这也是符合预期的,因为左边有个障碍物把路给堵住了,自然就没有路了。 

综上所述,在最上面和最左边分别添加一行一列辅助结点后,需要把除了[0, 1]位置的辅助结点全部初始化为0,dp[0][1] = 1

填表顺序:依然是从左往右,从上往下填表

返回值:返回dp[m][n],这是因为增加了辅助结点,dp表整体向右下方挪动了一个位置,所以dp表的[m, n]对应网格的[m - 1, n - 1]。

细节问题:此时dp表比网格多了一行一列,所以dp表的规模是(m + 1) x (n + 1)。另外,时刻记住下标的映射关系:dp表的[i, j]对应网格的[i - 1, j - 1]

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size(), n = obstacleGrid[0].size();

        // 创建dp表
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));

        // 初始化
        dp[0][1] = 1;

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

        // 返回结果
        return dp[m][n];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学习游泳的鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值