← 返回题目列表

不同路径问题如何用动态规划求解?(LeetCode 62)

高频 中等 第 3 / 33 题 更新于 2026/07/30
动态规划网格DP不同路径组合数学

简化版

一个 m×n 网格,机器人从左上角走到右下角,每次只能向右或向下,问有多少条不同路径。设 dp[i][j] 为「走到格子 (i,j) 的路径数」,因为只能从上面或左边过来,所以 dp[i][j] = dp[i-1][j] + dp[i][j-1]。第一行、第一列只有一条路(一直向右 / 一直向下),初始化为 1。答案 dp[m-1][n-1]。可压成一维数组,也有组合数公式 C(m+n-2, m-1)

详细版

int uniquePaths(int m, int n) {
    int[] dp = new int[n];
    Arrays.fill(dp, 1);              // 第一行:只有 1 条路径
    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            dp[j] += dp[j - 1];      // 新 dp[j](下面来) = 旧 dp[j](上面) + dp[j-1](左边)
        }
    }
    return dp[n - 1];
}
  • 状态dp[i][j] = 到达格子 (i,j) 的不同路径数。
  • 转移方程dp[i][j] = dp[i-1][j] + dp[i][j-1](从上或从左而来)。
  • 初始化:第一行、第一列全为 1(只能沿边直走,唯一路径)。
  • 一维压缩dp[j] += dp[j-1]——等号右边的 dp[j] 是上一行留下的值(上方),dp[j-1] 是本行刚更新的(左方)。
  • 复杂度:DP 为 O(m·n) 时间、O(n) 空间。

完整版教学

一、问题与网格 DP

不同路径:m×n 的网格,从左上角 (0,0) 到右下角 (m-1,n-1),每步只能向右向下,求路径总数。这是网格 DP(二维 DP) 的入门代表——很多「矩阵里从一角走到另一角」的题都套这个框架(最小路径和、带障碍的不同路径等)。

之所以能按行或按列填表,是因为移动方向只有向右和向下,状态依赖形成有向无环图。若允许向左或向上,路径可能形成环,“上方加左方”的拓扑顺序就不再覆盖全部合法路径。

二、状态定义与转移(从上或从左来)

定义状态dp[i][j] = 「从起点走到格子 (i,j) 的不同路径数」。

转移方程——盯住「到 (i,j) 的最后一步从哪来」。因为只能向右或向下走,所以到达 (i,j) 前的那一格,只可能是:

  • 上面的 (i-1, j)(最后一步向下);
  • 左边的 (i, j-1)(最后一步向右)。

这两种走法不重叠、且穷尽所有可能,于是路径数相加:

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

三、初始化:第一行、第一列都是 1

转移方程需要「上面」和「左边」,所以第一行(没有上面)和第一列(没有左边)要单独初始化:

  • 第一行任意 (0, j):只能从起点一路向右走到,唯一一条路径 → dp[0][j] = 1
  • 第一列任意 (i, 0):只能一路向下走到,也唯一 → dp[i][0] = 1

把边界铺成 1 之后,其余格子用转移方程从左上往右下逐个填,答案在 dp[m-1][n-1]

四、滚动数组压成一维

观察 dp[i][j] = dp[i-1][j] + dp[i][j-1]:算当前行时,只需要上一行的同列值本行左边的值。所以可以只留一维数组 dp[j],按行从上往下、每行从左往右更新:

dp[j] += dp[j-1];

更新前的 dp[j] 恰好是上一行dp[i-1][j](还没被这一行覆盖),dp[j-1]本行刚算好的左邻 dp[i][j-1],两者相加正好是新的 dp[i][j]。空间从 O(m·n) 降到 O(n)

五、组合数学解法 C(m+n-2, m-1)

这题还有 O(m+n) 的纯数学解。从左上到右下,总共要走 m-1 步向下、n-1 步向右,一共 (m-1)+(n-1) = m+n-2 步。一条路径就是这串「下/右」步骤的一个排列,本质是从 m+n-2 步里选出哪 m-1 步向下(其余自动是向右),所以路径数就是组合数:

C(m+n-2, m-1)

它比 DP 更快,但一旦网格里有障碍物(LeetCode 63),组合公式就失效了,只能回到 DP(把障碍格的 dp 置 0)。所以 DP 是更通用的解法,组合公式是本题无障碍时的特例优化。

六、状态语义、转移来源与遍历顺序

本题状态的完整含义是:dp[r][c] 表示到达格子 (r,c) 的路径数,最后一步只可能来自上方或左方。

转移过程是:dp[r][c]=dp[r-1][c]+dp[r][c-1];一维压缩时 dp[c] 更新前是上方,dp[c-1] 是左方。写代码前应逐个解释转移候选对应题目中的哪种最后选择,并确认这些候选互斥且覆盖全部可能。

定义状态:它概括哪段输入、处于什么决策阶段
列出选择:当前答案的最后一步有哪些来源
写出转移:由已计算的前驱组合当前答案
确定顺序:使用前驱之前不能覆盖它

数字推演:3×7 网格需走 2 次下、6 次右,共 8 步,选择 2 个下移位置得到 C(8,2)=28。

记忆钩子:DP 最容易错的不是 max/min,而是状态少记了信息或一维压缩后读到了本轮新值。

七、初始化、空间压缩与适用边界

实现边界是:首行首列在无障碍时为 1;有障碍时遇阻置 0,不能继续沿用;组合公式中间乘除要防溢出。

核对项本题答案
复杂度DP O(mn) 时间、O(n) 空间;无障碍组合数学 O(min(m,n)) 时间
基本状态必须能直接解释为规模 0 或最小输入的真实含义
遍历顺序由转移依赖决定,不能为了习惯随意正序/倒序
空间压缩仅在被覆盖状态之后不再需要时安全
结果位置可能是最后状态、全局最大值或多个终态的聚合

测试应覆盖空/最小输入、不可达状态、全零或负值、答案刚好发生在边界,以及会区分正序与倒序的样例。若需要恢复方案,不能只保留压缩后的数值,还要记录前驱或保留完整状态表。

八、常见误区与追问

  • 误区:第一行第一列都初始化 0。 起点到边界只有一条连续走法,应为 1。
  • 误区:一维 dp 每行要重新清零。 旧 dp[c] 正好代表上方路径数,不能清零。
  • 误区:组合数直接算阶乘最安全。 阶乘很快溢出,应边乘边除或用大整数。
  • 追问:为什么没有后效性? 到当前格后的未来只与位置有关,与具体路径无关。
  • 追问:有障碍怎样转移? 障碍格设 0,其他格仍累加上和左。
  • 追问:若可向四方向走怎么办? 可能形成环,简单网格 DP 的拓扑顺序不再成立。

九、加强记忆

不同路径是网格 DPdp[i][j] = 到 (i,j) 的路径数,只能从上或左来 → dp[i][j]=dp[i-1][j]+dp[i][j-1]。第一行第一列初始化为 1(沿边直走唯一路径),答案 dp[m-1][n-1]。可用一维滚动数组 dp[j]+=dp[j-1](上方 + 左方)压到 O(n) 空间。无障碍时有组合数捷径 C(m+n-2, m-1)(下/右步骤的排列),有障碍则必须用 DP。