【算法】动态规划 矩阵 :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.pswp.cn/diannao/89337.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/89337.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW调用Excel宏实现数据可视化

通过LabVIEW 的 ActiveX 接口&#xff0c;调用 Excel 应用程序&#xff0c;实现打开指定Excel 工作簿并运行其中宏&#xff08;如 “GraphData” 宏&#xff09;&#xff0c;将工作表数据以图表形式展示。通过 ActiveX 自动化技术&#xff0c;打通 LabVIEW 与 Excel 交互通道&a…

初始CNN(卷积神经网络)

卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;简称 CNN&#xff09;作为深度学习的重要分支&#xff0c;在图像识别、目标检测、语义分割等领域大放异彩。无论是手机上的人脸识别解锁&#xff0c;还是自动驾驶汽车对道路和行人的识别&#xff0c;背后都离…

深度解析Spring Bean生命周期:从字节码到可用对象的奇幻旅程

&#x1f331; 深度解析Spring Bean生命周期&#xff1a;从字节码到可用对象的奇幻旅程 你是否曾困惑&#xff1a;为什么PostConstruct有时不执行&#xff1f;为什么循环依赖报错如此难解&#xff1f;为什么AOP代理在某些场景失效&#xff1f; 本文将彻底拆解Spring Bean的16个…

MySQL 复合查询和内外连接 -- 子查询,多表查询,自连接,合并查询,表的内外连接

目录 1. 子查询 1.1 单行子查询 1.2 多行子查询 1.3 多列子查询 1.4 在 from 子句中使用子查询 2. 多表查询 3. 自连接 4. 合并查询 4.1 union 4.2 union all 5. 表的内连接 6. 表的外连接 下列先给出该博客中所用到的所有表的数据。 &#xff08;1&#xff09;部…

【STM32+LAN9252+HAL库】EtherCAT从站搭建 保姆级教程

目录 一、生成协议栈及XML文件 二、使用stm32CuboMX配置外设 三、协议栈移植 鉴于本人对EtherCAT的掌握程度十分有限&#xff0c;这篇文章仅作为我搭建基础从站的过程记录不做更多讲解。本文内容主要为SPI模式的基础搭建&#xff0c;更多深入的学习资料和细节&#xff0c;大家…

【LeetCode 热题 100】239. 滑动窗口最大值——(解法二)滑动窗口+单调队列

Problem: 239. 滑动窗口最大值 题目&#xff1a;给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。 【LeetCode 热题 100】239. 滑…

MySQL 8.0 连接 5.x 服务器认证问题

总的来说&#xff0c;答案是&#xff1a;可以&#xff0c;但是需要特别注意认证方式的兼容性问题。 MySQL 8.0 引入了新的默认认证插件 caching_sha2_password&#xff0c;而 MySQL 5.x&#xff08;及更早版本&#xff09;使用的是 mysql_native_password。当你用一个 8.0 的客…

Spring原理揭秘(一)

什么是spring&#xff1f; spring框架是一个轻量级的开源的JavaEE框架。 所谓轻量级则是&#xff1a;占用空间小&#xff0c;代码侵入性低&#xff0c;代码耦合度低&#xff0c;降低代码复杂度&#xff0c;可以轻易适配多种框架。 随着spring的不断发展&#xff0c;它所占用…

Visual Studio Code自用搜索技巧整理

多文件跨行搜索 用途 在多个日志文件中搜索跨行日志 方法 1.用VS Code打开待搜索文件所在的目录&#xff1b; 2.按快捷键&#xff08;CtrlShiftF&#xff09;打开全局搜索&#xff1b; 3.点击搜索框右侧的开启正则表达式&#xff1b; 4.输入正则表达式&#xff0c;例如&…

Axure PR 9 验证码登录 案例

大家好&#xff0c;我是大明同学。 这期内容&#xff0c;我们来用Axure来制作一个短信验证登录页面的小案例。 验证码登录小案例 创建手机号输入框所需的元件 1.打开一个新的 RP 文件并在画布上打开 Page 1。 2.在元件库中拖出一个矩形元件&#xff0c;选中矩形元件&#xf…

监听器模式

1. 问题背景 假设我们有一个 银行账户管理系统&#xff0c;该系统需要监控用户账户余额的变动&#xff0c;并在发生变动时&#xff0c;自动执行一些相关的操作&#xff0c;比如发送 余额变动通知&#xff08;如短信、邮件等&#xff09;。为了实现这一功能&#xff0c;我们希望…

帕鲁杯应急响应赛题:知攻善防实验室

一、背景信息 在这个跳跃的数字舞台上&#xff0c;数据安全成了政企单位稳航的重要压舱石。某政企单位&#xff0c;作为一艘驶向未来 的巨轮&#xff0c;对数据的把控丝毫不敢松懈。眼下&#xff0c;我们即将启航一场无与伦比的探险——“信息安全探索之 旅”。 这趟旅程的目的…

【硬核数学】2.2 深度学习的“微积分引擎”:自动微分与反向传播《从零构建机器学习、深度学习到LLM的数学认知》

欢迎来到本系列的第七篇文章。在上一章&#xff0c;我们用张量武装了我们的线性代数知识&#xff0c;学会了如何描述和操作神经网络中的高维数据流。我们知道&#xff0c;一个神经网络的“前向传播”过程&#xff0c;就是输入张量经过一系列复杂的张量运算&#xff08;矩阵乘法…

DAY 45 Tensorboard使用介绍

浙大疏锦行https://blog.csdn.net/weixin_45655710知识点回顾&#xff1a; tensorboard的发展历史和原理tensorboard的常见操作tensorboard在cifar上的实战&#xff1a;MLP和CNN模型 作业&#xff1a;对resnet18在cifar10上采用微调策略下&#xff0c;用tensorboard监控训练过程…

2023年全国硕士研究生招生考试英语(一)试题总结

文章目录 题型与分值分布完形填空错误 1&#xff1a;考察连词 or 前后内容之间的逻辑关系错误2&#xff1a;错误3&#xff1a;错误4&#xff1a;这个错得最有价值&#xff0c;因为压根没读懂错误5&#xff1a;学到的短语&#xff1a; 仔细阅读排序/新题型翻译小作文大作文 题型…

react-数据Mock实现——json-server

什么是mock&#xff1f; 在前后端分离的开发模式下&#xff0c;前端可以在没有实际后端接口的支持下先进行接口数据的模拟&#xff0c;进行正常的业务功能开发 json-server实现数据Mock json-server是一个node的包&#xff0c;可以在不到30秒内获得零编码的完整Mock服务 实现…

使用POI导入解析excel文件

首先校验 /*** 校验导入文件* param file 上传的文件* return 校验结果&#xff0c;成功返回包含成功状态的AjaxResult&#xff0c;失败返回包含错误信息的AjaxResult*/private AjaxResult validateImportFile(MultipartFile file) {if (file.isEmpty()) {return AjaxResult.er…

从0开始学习计算机视觉--Day06--反向传播算法

尽管解析梯度可以让我们省去巨大的计算量&#xff0c;但如果函数比较复杂&#xff0c;对这个损失函数进行微分计算会变得很困难。我们通常会用反向传播技术来递归地调用链式法则来计算向量每一个方向上的梯度。具体来说&#xff0c;我们将整个计算过程的输入与输入具体化&#…

企业流程知识:《学习观察:通过价值流图创造价值、消除浪费》读书笔记

《学习观察&#xff1a;通过价值流图创造价值、消除浪费》读书笔记 作者&#xff1a;迈克鲁斯&#xff08;Mike Rother&#xff09;&#xff0c;约翰舒克&#xff08;John Shook&#xff09; 出版时间&#xff1a;1999年 历史地位&#xff1a;精益生产可视化工具的黄金标准&am…

Day02_C语言IO进程线程

01.思维导图 02.将当前的时间写入到time. txt的文件中&#xff0c;如果ctrlc退出之后&#xff0c;在再次执行支持断点续写 1.2022-04-26 19:10:20 2.2022-04-26 19:10:21 3.2022-04-26 19:10:22 //按下ctrlc停止&#xff0c;再次执行程序 4.2022-04-26 20:00:00 5.2022-04-26 2…