LeetCode 62. 不同路径

131 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情.

62. 不同路径

题目描述

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

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?
在这里插入图片描述

解题思路

思路一: 排列组合

从左上角到右下角的过程中,我们需要移动 m+n−2 次,其中有 m−1 次向下移动,n−1 次向右移动。因此路径的总数,因此,从m+n-2次步数中选出m-1次向下的步数,或者n-1次向右的步数便是我们要的结果,因此不同的路径数就等于 Cm+n2m1C_{m+n-2}^{m-1}或者是Cm+n2n1C_{m+n-2}^{n-1},两者相等。

因此,我们根据公式直接求解即可: Cm+n2m1=(m+n2)!(m1)!(n1)!=(m+n2)(m+n3)...n(m1)!C_{m+n-2}^{m-1}=\frac{(m+n-2)!}{(m-1)!(n-1)!}=\frac{(m+n-2)(m+n-3)...n}{(m-1)!}

有了这个公式后,实现代码如下:
由于我们交换行列的值并不会对答案产生影响,因此我们总可以通过交换 m 和 n 使得 m ≤ n,这样空间复杂度降低至 O(min(m,n))

/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) {
    let numDown = m - 1, numRight = n - 1;

    if(numRight == 0 || numDown == 0){
        return 1;
    }

    let numSum = numRight + numDown, 
        min = Math.min(numDown, numRight);

    let numerator = 1 , denominator = 1;

    for (let i = 0; i < min; i++) {
        numerator *= (numSum - i);
        denominator *= (min - i);
    }

    return Math.floor(numerator / denominator); 
}; 
  • 空间复杂度: O(min(m,n))
  • 空间复杂度:O(1)

思路二: 动态规划

dp[i][j]表示从位置(i,j)到终点的路径个数

  1. 我们从终点开始,已知终点到终点只有一条路径,因此 初始值: dp[m-1][n-1]=1

  2. 然后考虑终点正上方: 终点的正上方想要到达终点,只能选择向下走,因此路径个数仍然为正下方的路径个数 dp[i][n-1] = dp[i+1][n-1]

  3. 考虑终点正左方: 同理,终点的正左方想要到达终点,只能选择向右走,因此路径个数仍然为正右方的路径个数 dp[m-1][i] = dp[m-1][i+1]

  4. 对于其他位置,当前路径数量等于 右方位置路径数 + 下方位置路径数 之和,即 dp[i][j] = dp[i+1][j]+ dp[i][j+1];

实现代码如下:

/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) { 
    if(m == 1 || n == 1){
        return 1;
    }
    const dp = Array(m).fill(0).map(() => Array(n).fill(0));

    dp[m-1][n-1] = 1;
    
    for (let i = n - 2; i >= 0; i--) {
        dp[m - 1][i] = 1;
    }

    for (let i = m - 2; i >= 0; i--) {
        dp[i][n - 1] = 1;
    }  

    for (let j = n -2; j >= 0; j--) {
        for (let i = m-2; i >= 0; i--) {
            // 当前路径数量等于 右边+下边的数量之和
            dp[i][j] = dp[i+1][j]+ dp[i][j+1];
        }
    }

    return dp[0][0]; 
}; 
  • 时间复杂度:O(mn)

  • 空间复杂度:O(mn)

优化一
由于dp[i][j] = dp[i+1][j]+ dp[i][j+1],因此只需要保留当前行与下一行的数据 (在动态方程中,即pre[j] = dp[i + 1][j])

优化代码如下:

/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) { 
    let prev = Array(n).fill(1), curr = Array(n).fill(1);

    for (let i = m - 2; i >= 0;i--){
        for (let j = n - 2; j >= 0; j--){
            curr[j] = curr[j + 1] + prev[j];
        }
        prev = [...curr];
    }
    return prev[0]; 
};  

优化后空间复杂度:O(2n);

优化二
只使用一个数组,cur[j] = cur[j + 1] + cur[j] 未赋值之前右边的cur[j] 始终表示当前行第i行的下一行第j列的值,赋值之后左边的cur[j]表示当前行第i行第j列的值,cur[j + 1] 表示当前行第i行第j + 1列的值(cur[j + 1] 在计算cur[j]之前就已经计算了,所以表示的是当前行而不是下一行

/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) { 
    let curr = Array(n).fill(1);

    for (let i = m - 2; i >= 0;i--){
        for (let j = n - 2; j >= 0; j--){
            curr[j] += curr[j + 1];
        }
    }
    return curr[0]; 
};  

优化后空间复杂度 O(n)

参考资料

leetcode.cn/problems/un…

leetcode.cn/problems/un…