【每日算法】LeetCode 62. 不同路径(多维动态规划)

对前端开发者而言,学习算法绝非为了"炫技"。它是你从"页面构建者"迈向"复杂系统设计者"的关键阶梯。它将你的编码能力从"实现功能"提升到"设计优雅、高效解决方案"的层面。从现在开始,每天投入一小段时间,结合前端场景去理解和练习,你将会感受到自身技术视野和问题解决能力的质的飞跃。------ 算法:资深前端开发者的进阶引擎

LeetCode 62. 不同路径

1. 题目描述

一个机器人位于一个 m × n 网格的左上角(起始点标记为"Start")。机器人每次只能向下或向右移动一步。机器人试图到达网格的右下角(标记为"Finish")。问总共有多少条不同的路径?

示例 1:

复制代码
输入: m = 3, n = 2
输出: 3
解释:
从左上角到右下角一共有 3 条路径:
1. 右 → 右 → 下
2. 右 → 下 → 右
3. 下 → 右 → 右

示例 2:

复制代码
输入: m = 7, n = 3
输出: 28

提示:

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

2. 问题分析

2.1 问题本质

这是一个典型的网格路径计数问题,属于动态规划的经典场景。我们可以将网格看作一个二维数组,机器人的移动方向被限制为只能向下或向右,这确保了路径不会形成环路。

2.2 前端视角思考

前端开发者经常处理类似的网格布局问题:

  • 页面布局网格系统(如Bootstrap栅格)
  • Canvas/Canvas2D中的坐标系统
  • 游戏开发中的地图网格(如寻路算法)
  • CSS Grid布局中的单元格遍历

理解此类问题的解决方法,有助于处理如:

  • 计算DOM元素的不同排列方式
  • 可视化图表中的路径规划
  • 富文本编辑器的光标移动计算

3. 解题思路

3.1 思路概览

对于此类网格路径问题,常见的解题思路有:

  1. 动态规划(DP):最优解,时间复杂度 O(m×n),空间复杂度可优化
  2. 数学组合数法:利用组合数学公式直接计算
  3. 递归回溯:暴力搜索所有路径(性能差,仅适用于小规模问题)

3.2 思路详解

3.2.1 动态规划(最优解)

核心思想:到达每个格子的路径数 = 到达上方格子的路径数 + 到达左方格子的路径数

状态定义

  • dp[i][j] 表示从起点 (0,0) 到达格子 (i,j) 的不同路径数

状态转移方程

复制代码
dp[i][j] = dp[i-1][j] + dp[i][j-1]

边界条件

  • 第一行所有格子:只有一种路径(一直向右)
  • 第一列所有格子:只有一种路径(一直向下)
3.2.2 组合数学法

核心思想 :从起点到终点需要移动 m+n-2 步,其中向下 m-1 步,向右 n-1 步。路径总数等于从 m+n-2 步中选择 m-1 步向下(或 n-1 步向右)的组合数。

公式

复制代码
C(m+n-2, m-1) = C(m+n-2, n-1)
3.2.3 递归回溯

核心思想:深度优先搜索所有可能路径,但会重复计算,效率低。

4. 代码实现

4.1 动态规划(基础版)

javascript 复制代码
/**
 * 动态规划基础版 - 二维数组存储
 * 时间复杂度: O(m×n)
 * 空间复杂度: O(m×n)
 */
function uniquePathsBasic(m, n) {
  // 创建 m×n 的二维数组,初始化为 1(因为第一行和第一列都是1)
  const dp = Array(m).fill().map(() => Array(n).fill(1));
  
  // 从第2行第2列开始计算(因为第一行和第一列已经初始化为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];
}

4.2 动态规划(空间优化版)

javascript 复制代码
/**
 * 动态规划空间优化版 - 滚动数组
 * 时间复杂度: O(m×n)
 * 空间复杂度: O(n) 或 O(m),取决于实现
 * 
 * 原理:每次计算新的一行时,只需要上一行的数据
 */
function uniquePathsOptimized(m, n) {
  // 如果m或n为1,只有1条路径
  if (m === 1 || n === 1) return 1;
  
  // 使用一维数组存储当前行的路径数
  // 初始化为1,代表第一行的所有格子都只有1条路径
  let dp = Array(n).fill(1);
  
  // 从第2行开始计算
  for (let i = 1; i < m; i++) {
    // 从第2列开始计算
    for (let j = 1; j < n; j++) {
      // dp[j] 更新前代表上一行第j列的值(即上方格子)
      // dp[j-1] 代表当前行第j-1列的值(即左方格子)
      dp[j] = dp[j] + dp[j-1];
    }
  }
  
  // 返回最后一列的值
  return dp[n-1];
}

4.3 组合数学法

javascript 复制代码
/**
 * 组合数学法
 * 时间复杂度: O(min(m, n))
 * 空间复杂度: O(1)
 * 
 * 使用公式: C(m+n-2, m-1) = C(m+n-2, n-1)
 * 选择较小的维度计算以提高效率
 */
function uniquePathsMath(m, n) {
  // 确保计算组合数时使用较小的数,减少计算量
  const totalSteps = m + n - 2; // 总步数
  const downSteps = m - 1;      // 向下步数
  const rightSteps = n - 1;     // 向右步数
  const k = Math.min(downSteps, rightSteps); // 选择较小的计算
  
  // 计算组合数 C(totalSteps, k)
  let result = 1;
  
  // 使用组合数计算公式: C(n, k) = n! / (k! * (n-k)!)
  // 可以简化为: C(n, k) = ∏(i=1 to k) (n - k + i) / i
  for (let i = 1; i <= k; i++) {
    result = result * (totalSteps - k + i) / i;
  }
  
  // 注意:由于JavaScript浮点数精度问题,结果可能不是整数
  // 但在这个问题中,路径数一定是整数,所以可以取整
  return Math.round(result);
}

// 示例验证
console.log(uniquePathsMath(3, 2)); // 输出: 3
console.log(uniquePathsMath(7, 3)); // 输出: 28
console.log(uniquePathsMath(10, 10)); // 输出: 48620

4.4 递归法(仅用于理解)

javascript 复制代码
/**
 * 递归法 - 仅用于理解问题,实际会超时
 * 时间复杂度: O(2^(m+n)),指数级增长
 * 空间复杂度: O(m+n),递归栈深度
 */
function uniquePathsRecursive(m, n) {
  // 边界条件:到达第一行或第一列的任意位置,只有1条路径
  if (m === 1 || n === 1) {
    return 1;
  }
  
  // 递归计算:向右走 + 向下走
  return uniquePathsRecursive(m - 1, n) + uniquePathsRecursive(m, n - 1);
}

// 示例:注意只能用于小规模计算
console.log(uniquePathsRecursive(3, 2)); // 输出: 3
// console.log(uniquePathsRecursive(7, 3)); // 计算较慢,不推荐

4.5 递归+记忆化(优化递归)

javascript 复制代码
/**
 * 递归+记忆化搜索
 * 时间复杂度: O(m×n)
 * 空间复杂度: O(m×n)
 * 
 * 使用备忘录避免重复计算
 */
function uniquePathsMemo(m, n) {
  // 创建备忘录,初始化为0表示未计算
  const memo = Array(m + 1).fill().map(() => Array(n + 1).fill(0));
  
  // 辅助递归函数
  function dfs(i, j) {
    // 边界条件
    if (i === 1 || j === 1) {
      return 1;
    }
    
    // 如果已经计算过,直接返回
    if (memo[i][j] !== 0) {
      return memo[i][j];
    }
    
    // 计算并存储结果
    memo[i][j] = dfs(i - 1, j) + dfs(i, j - 1);
    return memo[i][j];
  }
  
  return dfs(m, n);
}

// 示例
console.log(uniquePathsMemo(3, 2)); // 输出: 3
console.log(uniquePathsMemo(7, 3)); // 输出: 28

5. 各实现思路对比

方法 时间复杂度 空间复杂度 优点 缺点 适用场景
动态规划(基础) O(m×n) O(m×n) 思路直观,易于理解 空间占用较大 通用场景,教学演示
动态规划(优化) O(m×n) O(min(m,n)) 空间效率高 代码稍复杂 实际生产环境
组合数学法 O(min(m,n)) O(1) 时间复杂度最低 可能溢出,需处理大数 m,n较大时
递归法 O(2^(m+n)) O(m+n) 代码简洁 指数级时间,无法实用 仅用于理解问题
递归+记忆化 O(m×n) O(m×n) 递归思路,易于理解 递归栈可能溢出 中等规模问题

6. 总结与扩展

6.1 通用解题模板

对于网格路径类问题,可以遵循以下模板:

javascript 复制代码
// 动态规划通用框架
function gridPathDP(m, n) {
  // 1. 创建DP数组(考虑空间优化)
  const dp = Array(m).fill().map(() => Array(n).fill(0));
  
  // 2. 初始化边界条件
  for (let i = 0; i < m; i++) dp[i][0] = 1;
  for (let j = 0; j < n; j++) dp[0][j] = 1;
  
  // 3. 状态转移
  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];
    }
  }
  
  // 4. 返回结果
  return dp[m-1][n-1];
}

6.2 前端应用场景

  1. UI布局计算:计算不同屏幕尺寸下的布局排列方式
  2. 游戏开发:网格地图的寻路算法(如A*算法的简化版)
  3. 可视化:力导向图中节点间的不同连接方式
  4. 路由规划:计算从A点到B点的不同导航路径

6.3 类似题目推荐

  1. LeetCode 63. 不同路径 II - 网格中有障碍物

    • 需要在动态规划中处理障碍物情况
    • 状态转移时,遇到障碍物则路径数为0
  2. LeetCode 64. 最小路径和 - 寻找最小路径和

    • 从路径计数变为路径和最小化
    • 状态转移:dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
  3. LeetCode 120. 三角形最小路径和 - 三角形网格

    • 二维DP的变体,需要注意边界条件
  4. LeetCode 221. 最大正方形 - 寻找最大正方形面积

    • 同样是网格DP,但状态定义和转移更复杂
相关推荐
车企求职辅导2 小时前
新能源汽车零部件全品类汇总
人工智能·算法·车载系统·自动驾驶·汽车·智能驾驶·智能座舱
HUST2 小时前
C 语言 第九讲:函数递归
c语言·开发语言·数据结构·算法·c#
yaoh.wang2 小时前
力扣(LeetCode) 119: 杨辉三角 II - 解法思路
数据结构·python·算法·leetcode·面试·职场和发展·跳槽
console.log('npc')2 小时前
vue3文件上传弹窗,图片pdf,word,结合预览kkview
前端·javascript·vue.js·pdf·word
客梦2 小时前
数据结构--最小生成树
数据结构·笔记
CoderCodingNo2 小时前
【GESP】C++五级真题(埃氏筛思想考点) luogu-B3929 [GESP202312 五级] 小杨的幸运数
数据结构·c++·算法
inferno2 小时前
CSS 基础(第二部分)
前端·css
BD_Marathon2 小时前
Router_路由传参
前端·javascript·vue.js
机器学习之心2 小时前
基于PSO-GA混合算法的施工进度计划多目标优化,以最小化总成本并实现资源均衡,满足工期约束和资源限制,MATLAB代码
算法·matlab·多目标优化·pso-ga混合算法