62. 不同路径 (unique paths)

3,858 阅读2分钟

"斐波那契,动态规划开始的地方"

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情

62. 不同路径 题目描述:一个机器人位于一个 m×nm \times n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?

看图示例
输入: m=3,n=7m = 3, n = 7
输出: 2828

中规中矩的动态规划

最优子结构? 假设我们知道走到 (m1,n)(m-1,n) 节点共有 Sm1,nS_{m-1,n} 种路径,那么从 (m1,n)(m-1,n) 节点走到 (m,n)(m,n) 节点仅有一种走法(向下移动一步);假设我们也知道走到 (m,n1)(m,n-1) 节点共有 Sm,n1S_{m,n-1} 种路径,那么从 (m,n1)(m,n-1) 节点走到 (m,n)(m,n) 节点仅有一种走法(向右移动一步)。所以走到 (m,n)(m,n) 节点的不同路径数就等于走到 (m1,n)(m-1,n) 节点的不同路径数加上走到 (m,n1)(m,n-1) 节点的不同路径数,即 Sm,n=Sm1,n+Sm,n1S_{m,n} = S_{m-1,n} + S_{m, n-1} 就是我们要找的最优子结构。

1、确定 dp 状态数组

定义 dp[i][j]dp[i][j] 是走到 (i,j)(i,j) 节点的不同路径数,其中 i[0,m),j[0,n)i \in [0,m),j \in [0,n)

2、确定 dp 状态方程

与之前讨论的最优子结构即为 dpdp 状态方程,

dp[i][j]=dp[i1][j]+dp[i][j1]dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

3、确定 dp 初始状态

i=0i=0 时,对于任意 j[0,n)j \in [0,n),都只有一种路径(只能横向走),故 dp[0][j]=1dp[0][j] = 1

j=0j=0 时,对于任意 i[0,m)i \in [0, m),都只有一种路径 (只能纵向走),故 dp[i][0]=1dp[i][0] = 1

NOTE: dp[0][0]dp[0][0] 是一个特殊的点(起始点),在后期的计算中,dp[0][0]dp[0][0] 并不参与,所以无须纠结 dp[0][0]=0dp[0][0] = 011 的问题。为方便 dpdp 数组初始化,统一按照 dp[0][0]=1dp[0][0] = 1 处理。

4、确定遍历顺序

  • 第一层循环从 i=1i=1i=m1i=m-1;

  • 第二层循环从 j=1j=1j=n1j=n-1

NOTE: 先遍历 ii 后遍历 jj,或者先遍历 jj 后遍历 ii 均可。

5、确定最终返回值

回归到状态定义中,dp[m1][n1]dp[m-1][n-1] 是走到 (m,n)(m,n) 节点的不同路径数。

6、代码示例

/**
 * 空间复杂度 O(m*n)
 * 时间复杂度 O(m*n)
 */
function uniquePaths(m: number, n: number): number {
    const dp = Array.from({ length: m }, () => new Array(n).fill(1));

    for(let i = 1; i < m; i++) {
        for(let j = 1; j < n; j++) {
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }

    return dp[m - 1][n - 1];
}

调整遍历顺序

/**
 * 空间复杂度 O(m*n)
 * 时间复杂度 O(m*n)
 */
function uniquePaths(m: number, n: number): number {
    const dp = Array.from({ length: m }, () => new Array(n).fill(1));

     for(let j = 1; j < n; j++) {
        for(let i = 1; i < m; i++) {
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }

    return dp[m - 1][n - 1];
}

思维提升-排列组合

我们发现在 m×nm \times n 的格子中,一定要向下走 m1m - 1 步才能到达第 mm 行,一定要右走 n1n-1 步才能到达第 nn 列。所以这道题目就变成了一道高中的排列组合题目,从 m+n2m + n - 2 里选择 m1m - 1 共有多少种方法,即 Cm+n2m1C_{m+n-2}^{m - 1}Cm+n2n1C_{m+n-2}^{n - 1}

代码示例

/**
 * 空间复杂度 O(m*n)
 * 时间复杂度 O(m*n)
 */
function uniquePaths(m: number, n: number): number {
    return factorial(m + n - 2) / factorial(m - 1) / factorial(n - 1);
};

function factorial(n: number) {
    if (n <= 1) {
        return 1;
    }

    return n * factorial(n - 1);
}

参考

# 重识动态规划