当前位置: 首页 > news >正文

【算法】动态规划 矩阵 :62. 不同路径

62. 不同路径

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

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

问总共有多少条不同的路径?

在这里插入图片描述
在这里插入图片描述

实现

下面分别给出基于动态规划的空间优化写法——C++ 与 Python 两种实现,均为 O ( m n ) O(mn) O(mn) 时间、 O ( n ) O(n) O(n) 空间:

#include <bits/stdc++.h>
using namespace std;// 计算从 (0,0) 到 (m-1,n-1) 的不同路径数
int uniquePaths(int m, int n) {// dp[j] 表示当前行到达列 j 的路径数vector<int> dp(n, 1);// 第一行所有 dp[j] 初始化为 1(只能一直向右走)for (int i = 1; i < m; ++i) {// 第 0 列始终为 1(只能一直向下走)for (int j = 1; j < n; ++j) {// 来自上方 (上一行同列) + 来自左方 (当前行前一列)dp[j] += dp[j - 1];}}return dp[n - 1];
}int main() {int m, n;// 读入 m, ncin >> m >> n;cout << uniquePaths(m, n) << "\n";return 0;
}
def unique_paths(m: int, n: int) -> int:"""dp[j] 表示当前行到达列 j 的路径数。初始 dp[:] = [1,1,...,1],代表第 0 行只能一直向右。"""dp = [1] * nfor _ in range(1, m):# dp[0] 保持为 1for j in range(1, n):dp[j] += dp[j - 1]return dp[-1]if __name__ == "__main__":# 输入格式:m nm, n = map(int, input().split())print(unique_paths(m, n))

思路回顾:

  1. 定义状态:用一维数组 dp[j] 表示「到达当前行第 j j j 列的路径数」。

  2. 边界条件:第一行所有 dp[j]=1(只能向右),第一列隐式保持 dp[0]=1(只能向下)。

  3. 状态转移:对于第 i i i 行的第 j j j 列,有

    d p [ j ] ← d p [ j ] ( 上一行同列 ) + d p [ j − 1 ] ( 当前行前一列 ) . dp[j] \leftarrow dp[j]\;(\text{上一行同列}) \;+\; dp[j-1]\;(\text{当前行前一列})\,. dp[j]dp[j](上一行同列)+dp[j1](当前行前一列).

  4. 结果保存在 dp[n-1]dp[-1]

这种「滚动数组」的做法将空间复杂度从 O ( m n ) O(mn) O(mn) 降至 O ( n ) O(n) O(n)




通俗解释

下面我们用最直观的“走格子填数字”的方式来理解这道题的动态规划解法。


一、问题回顾

  • 机器人在一个 m × n m\times n m×n 的网格里,只能「向右」或「向下」走一步,问从左上角到右下角一共有多少条不同的路径。

二、最通俗的思路:给每个格子“打分”

  1. 把网格想象成一个表格,我们给每个格子 ( i , j ) (i,j) (i,j) 一个数 dp[i][j],它代表「到达 ( i , j ) (i,j) (i,j) 的不同走法数」。

  2. 为什么格子 ( i , j ) (i,j) (i,j) 的分数==它上面格子 + 它左边格子的分数?

    • 因为机器人最后一步,要么从上面 ( i − 1 , j ) (i-1,j) (i1,j) 下来,要么从左边 ( i , j − 1 ) (i,j-1) (i,j1) 右来。

    • 所以,

      d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] . dp[i][j] = dp[i-1][j] + dp[i][j-1]. dp[i][j]=dp[i1][j]+dp[i][j1].

  3. 边界条件:第一行和第一列只能走一条路线

    • 第一行 ( 0 , 0 ) → ( 0 , 1 ) → ⋯ (0,0)\to(0,1)\to\cdots (0,0)(0,1) 全是「一直向右」,所以它们 dp[0][*] = 1
    • 第一列 ( 0 , 0 ) → ( 1 , 0 ) → ⋯ (0,0)\to(1,0)\to\cdots (0,0)(1,0) 全是「一直向下」,所以它们 dp[*][0] = 1

三、举例:3×3 网格

我们把 dp 表格画出来(行列都从 0 开始):

i\j012
0111
11
21
  • 第一行、第一列先填 1。

接下来按行、按列填:

  1. ( 1 , 1 ) (1,1) (1,1):上面是 ( 0 , 1 ) = 1 (0,1)=1 (0,1)=1,左边是 ( 1 , 0 ) = 1 (1,0)=1 (1,0)=11+1=2
  2. ( 1 , 2 ) (1,2) (1,2):上面是 ( 0 , 2 ) = 1 (0,2)=1 (0,2)=1,左边是 ( 1 , 1 ) = 2 (1,1)=2 (1,1)=21+2=3
  3. ( 2 , 1 ) (2,1) (2,1):上面是 ( 1 , 1 ) = 2 (1,1)=2 (1,1)=2,左边是 ( 2 , 0 ) = 1 (2,0)=1 (2,0)=12+1=3
  4. ( 2 , 2 ) (2,2) (2,2):上面是 ( 1 , 2 ) = 3 (1,2)=3 (1,2)=3,左边是 ( 2 , 1 ) = 3 (2,1)=3 (2,1)=33+3=6

最终表格:

i\j012
0111
1123
2136

右下角 ( 2 , 2 ) (2,2) (2,2) 就是 6,也就是 3×3 网格的答案。


四、滚动数组优化到只用一维

其实我们只需要「上一行」和「当前行」信息,就能把二维压成一维。

  • 用一个长度为 n n n 的数组 dp[j] 表示「当前行到第 j j j 列的路径数」。

  • 每次更新时:

    dp[j] = dp[j](上面的旧值) + dp[j-1](左边刚更新的值)
    
  • 第一行先把 dp[:] = [1,1,1,...] 初始化好。


五、代码实现

1. C++ 版( O ( n ) O(n) O(n) 空间)

#include <bits/stdc++.h>
using namespace std;int uniquePaths(int m, int n) {vector<int> dp(n, 1);  // 第一行:到每列只有 1 种走法for (int i = 1; i < m; ++i) {       // 从第 2 行开始for (int j = 1; j < n; ++j) {   // 第一列 dp[0] 始终是 1dp[j] += dp[j - 1];         // 上 + 左}}return dp[n - 1];
}int main() {int m, n;cin >> m >> n;cout << uniquePaths(m, n) << "\n";return 0;
}

2. Python 版( O ( n ) O(n) O(n) 空间)

def unique_paths(m: int, n: int) -> int:dp = [1] * n           # 第一行:全 1for _ in range(1, m):  # 从第 2 行开始for j in range(1, n):dp[j] += dp[j - 1]  # 上 + 左return dp[-1]if __name__ == "__main__":m, n = map(int, input().split())print(unique_paths(m, n))

六、结题思路小结

  1. 状态定义dp[i][j](或滚动数组里的 dp[j])表示「到达格子 ( i , j ) (i,j) (i,j) 的路径数」。
  2. 状态转移dp[i][j] = dp[i-1][j] + dp[i][j-1] —— 最后一部要么从上面下来,要么从左边来。
  3. 边界:第一行/第一列只能一直往右/往下,路径数都是 1。
  4. 空间优化:因为只用到「上一行」信息,可以把二维压成一维,dp[j] = dp[j] + dp[j-1]

这样逻辑就很清晰:

  • 想象你在走格子,每个格子都“接收”来自上方和左方的走法数;
  • 一圈圈填下去,最后在终点就能看到总路数。

希望这样的流程化、填表式讲解能帮助你彻底搞懂!

http://www.dtcms.com/a/264018.html

相关文章:

  • Android屏幕共享+WebSocket实现传输截图
  • tree 命令集成到 Git Bash:可视化目录结构的指南
  • 成为一名大数据平台SRE需要具备哪些基础技能-附录
  • 为什么js是单线程?
  • SpringMVC--使用RESTFul实现用户管理系统
  • MySQL 8.4 备份与恢复完全指南
  • 软件测试期末复习之白盒测试
  • 将svn项目迁移到git
  • 技术学习_人工智能_1_神经网络是如何实现的?
  • 【算法】动态规划 斐波那契类型: 740. 删除并获得点数
  • Vue 3.x 使用 “prerender-spa-plugin ” 预渲染实现网站 SEO 优化
  • 读Vista
  • AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年7月1日第125弹
  • 数据结构学习——图
  • AiPy +创宇智脑 MCP+Doubao-1.6:IP 风险调查效率显著提高
  • 顶级SCI极光优化算法!PLO-Transformer-GRU多变量时间序列预测,Matlab实现
  • 借助工具给外语视频加双语字幕的实用指南​
  • 【Maven 】 <resources> 配置中排除 fonts/** 目录无效,可能是由于以下原因及解决方案:
  • 坚石ET ARM加密狗复制模拟介绍
  • gis服务器geoserver的下载与安装
  • 分布式爬虫数据存储开发实战
  • 开源模型应用落地-OpenAI Agents SDK-集成Qwen3-8B-探索input_guardrail 的创意应用(五)
  • WPF学习笔记(19)控件模板ControlTemplate与内容呈现ContentPresenter
  • 电子面单系统开发全解析
  • 创建对象的步骤
  • docker desktop部署本地gitlab服务
  • JVM 知识点
  • 数据结构day7——文件IO
  • MapReduce分布式计算框架:从原理到实战
  • 7.可视化的docker界面——portainer