文章目录

  • 63. 不同路径 II
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 核心思想:动态规划(避开障碍)
      • 算法流程
      • 复杂度分析
      • 边界与细节
      • 方法对比
    • 代码实现
      • Go 实现(含二维DP / 一维DP / 记忆化)
    • 测试用例设计
    • 小结
    • 完整题解代码

63. 不同路径 II

题目描述

给定一个 m x n 的整数数组 grid。一个机器人初始位于 左上角(即 grid[0][0])。机器人尝试移动到 右下角(即 grid[m - 1][n - 1])。机器人每次只能向下或者向右移动一步。

网格中的障碍物和空位置分别用 1 和 0 来表示。机器人的移动路径中不能包含 任何 有障碍物的方格。

返回机器人能够到达右下角的不同路径数量。

测试用例保证答案小于等于 2 * 109。

示例 1:

在这里插入图片描述

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:

  1. 向右 -> 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右 -> 向右

示例 2:

在这里插入图片描述

输入:obstacleGrid = [[0,1],[0,0]]
输出:1

提示:

  • m == obstacleGrid.length
  • n == obstacleGrid[i].length
  • 1 <= m, n <= 100
  • obstacleGrid[i][j] 为 0 或 1

解题思路

核心思想:动态规划(避开障碍)

与“接雨水”一样,本题也可通过多种思路来解决,但最优且主流的做法是动态规划。定义 dp[i][j] 为从起点 (0,0) 走到 (i,j) 的不同路径数:

  • (i,j) 为障碍(值为1)dp[i][j] = 0
  • 否则dp[i][j] = dp[i-1][j] + dp[i][j-1](只能从上方或左方到达)

初始条件:

  • 若起点或终点是障碍,则答案为 0
  • dp[0][0] = 1(前提:起点无障碍)

算法流程

graph TDA[开始] --> B[输入 obstacleGrid]B --> C{起点/终点是否为障碍}C -->|是| D[返回 0]C -->|否| E[初始化 dp]E --> F[按行填表]F --> G{遇到障碍?}G -->|是| H[置 0]G -->|否| I[dp[i][j]=dp[i-1][j]+dp[i][j-1]]H --> FI --> FF --> J[返回 dp[m-1][n-1]]

复杂度分析

  • 时间复杂度:O(m·n)
  • 空间复杂度
    • 二维 DP:O(m·n)
    • 一维DP(空间优化):O(n)

边界与细节

  • 起点或终点为障碍:直接返回 0
  • 首行/首列初始化时,若遇到障碍,其后全部为 0
  • 一维 DP 时,遇到障碍位置要将 dp[j]0

方法对比

graph TDA[方法对比] --> B[二维DP]A --> C[一维DP]A --> D[记忆化DFS]B --> E[易理解 空间O(mn)]C --> F[最优空间 O(n)]D --> G[实现直观 递归栈]

代码实现

Go 实现(含二维DP / 一维DP / 记忆化)

package mainimport ("fmt"
)func uniquePathsWithObstaclesDP2D(obstacleGrid [][]int) int { /* 见 main.go */ return 0 }
func uniquePathsWithObstaclesDP1D(obstacleGrid [][]int) int { /* 见 main.go */ return 0 }
func uniquePathsWithObstaclesMemo(obstacleGrid [][]int) int { /* 见 main.go */ return 0 }func main() {grid := [][]int{{0,0,0},{0,1,0},{0,0,0}}fmt.Println(uniquePathsWithObstaclesDP1D(grid)) // 输出: 2
}

完整可运行代码与测试见 main.go

测试用例设计

测试用例
基础功能
边界情况
示例1
示例2
起点障碍
终点障碍
单行/单列

建议覆盖:

  • 起点/终点为障碍
  • 单行、单列、1x1
  • 中间若干障碍导致路径阻断
  • 无障碍的普通网格

小结

  • 本题的本质是“有障碍的网格路径计数”,动态规划最稳妥
  • 一维 DP 可在不影响可读性的情况下,将空间优化到 O(n)
  • 记忆化搜索更贴近推导,便于理解转移关系

完整题解代码

package mainimport ("fmt""strings""time"
)// 解法一:二维动态规划(直观)
// 状态定义:
//
//	dp[i][j] 表示到达单元格 (i, j) 的不同路径数
//
// 状态转移:
//
//	若 obstacleGrid[i][j] 为障碍(1),则 dp[i][j] = 0
//	否则 dp[i][j] = dp[i-1][j] + dp[i][j-1](来自上方和左方)
//
// 初始条件:
//
//	dp[0][0] = 1(前提是 obstacleGrid[0][0] == 0)
//
// 答案:
//
//	dp[m-1][n-1]
func uniquePathsWithObstaclesDP2D(obstacleGrid [][]int) int {m := len(obstacleGrid)if m == 0 {return 0}n := len(obstacleGrid[0])if n == 0 {return 0}if obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1 {return 0}dp := make([][]int, m)for i := 0; i < m; i++ {dp[i] = make([]int, n)}dp[0][0] = 1// 初始化首行for j := 1; j < n; j++ {if obstacleGrid[0][j] == 1 {dp[0][j] = 0} else {dp[0][j] = dp[0][j-1]}}// 初始化首列for i := 1; i < m; i++ {if obstacleGrid[i][0] == 1 {dp[i][0] = 0} else {dp[i][0] = dp[i-1][0]}}// 填表for i := 1; i < m; i++ {for j := 1; j < n; j++ {if obstacleGrid[i][j] == 1 {dp[i][j] = 0continue}dp[i][j] = dp[i-1][j] + dp[i][j-1]}}return dp[m-1][n-1]
}// 解法二:一维动态规划(空间优化到 O(n))
// 复用一行 dp:dp[j] 表示当前行列 j 的到达路径数
// 当遇到障碍时将 dp[j] 置 0;否则 dp[j] += dp[j-1]
func uniquePathsWithObstaclesDP1D(obstacleGrid [][]int) int {m := len(obstacleGrid)if m == 0 {return 0}n := len(obstacleGrid[0])if n == 0 {return 0}if obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1 {return 0}dp := make([]int, n)dp[0] = 1for i := 0; i < m; i++ {for j := 0; j < n; j++ {if obstacleGrid[i][j] == 1 {dp[j] = 0} else if j > 0 {dp[j] += dp[j-1]}}}return dp[n-1]
}// 解法三:记忆化搜索(自顶向下)
// 注意:在最坏情况下与 DP 等价,但实现上更贴近转移关系
func uniquePathsWithObstaclesMemo(obstacleGrid [][]int) int {m := len(obstacleGrid)if m == 0 {return 0}n := len(obstacleGrid[0])if n == 0 {return 0}if obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1 {return 0}memo := make([][]int, m)for i := 0; i < m; i++ {memo[i] = make([]int, n)for j := 0; j < n; j++ {memo[i][j] = -1}}var dfs func(i, j int) intdfs = func(i, j int) int {if i < 0 || j < 0 {return 0}if obstacleGrid[i][j] == 1 {return 0}if i == 0 && j == 0 {return 1}if memo[i][j] != -1 {return memo[i][j]}memo[i][j] = dfs(i-1, j) + dfs(i, j-1)return memo[i][j]}return dfs(m-1, n-1)
}// 辅助:对比多个算法的结果,确保一致性
func runTestCases() {type testCase struct {grid     [][]intexpected intdesc     string}tests := []testCase{{[][]int{{0, 0, 0}, {0, 1, 0}, {0, 0, 0}}, 2, "示例1:中间有障碍"},{[][]int{{0, 1}, {0, 0}}, 1, "示例2:两行两列"},{[][]int{{0}}, 1, "1x1 无障碍"},{[][]int{{1}}, 0, "1x1 有障碍"},{[][]int{{0, 0, 0, 0}}, 1, "单行无障碍"},{[][]int{{0, 1, 0, 0}}, 0, "单行有障碍阻断"},{[][]int{{0}, {0}, {0}}, 1, "单列无障碍"},{[][]int{{0}, {1}, {0}}, 0, "单列有障碍阻断"},{[][]int{{0, 0}, {1, 0}}, 1, "简单障碍-右下可达"},{[][]int{{0, 0}, {0, 1}}, 0, "终点为障碍"},{[][]int{{1, 0}, {0, 0}}, 0, "起点为障碍"},}fmt.Println("=== 63. 不同路径 II - 测试 ===")for i, tc := range tests {r1 := uniquePathsWithObstaclesDP2D(cloneGrid(tc.grid))r2 := uniquePathsWithObstaclesDP1D(cloneGrid(tc.grid))r3 := uniquePathsWithObstaclesMemo(cloneGrid(tc.grid))ok := (r1 == tc.expected) && (r2 == tc.expected) && (r3 == tc.expected)status := "✅"if !ok {status = "❌"}fmt.Printf("用例 %d: %s\n", i+1, tc.desc)fmt.Printf("输入: %v\n", tc.grid)fmt.Printf("期望: %d\n", tc.expected)fmt.Printf("二维DP: %d, 一维DP: %d, 记忆化: %d\n", r1, r2, r3)fmt.Printf("结果: %s\n", status)fmt.Println(strings.Repeat("-", 40))}
}// 简单性能比较
func benchmark() {fmt.Println("\n=== 性能对比(粗略) ===")big := make([][]int, 100)for i := range big {big[i] = make([]int, 100)}start := time.Now()_ = uniquePathsWithObstaclesDP2D(big)d1 := time.Since(start)start = time.Now()_ = uniquePathsWithObstaclesDP1D(big)d2 := time.Since(start)start = time.Now()_ = uniquePathsWithObstaclesMemo(big)d3 := time.Since(start)fmt.Printf("二维DP: %v\n", d1)fmt.Printf("一维DP: %v\n", d2)fmt.Printf("记忆化: %v\n", d3)
}func cloneGrid(g [][]int) [][]int {m := len(g)res := make([][]int, m)for i := 0; i < m; i++ {res[i] = append([]int(nil), g[i]...)}return res
}func main() {fmt.Println("63. 不同路径 II")fmt.Println(strings.Repeat("=", 40))runTestCases()benchmark()
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/92419.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/92419.shtml
英文地址,请注明出处:http://en.pswp.cn/web/92419.shtml

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

相关文章

C++ 模拟实现 map 和 set:掌握核心数据结构

C 模拟实现 map 和 set&#xff1a;掌握核心数据结构 文章目录C 模拟实现 map 和 set&#xff1a;掌握核心数据结构一、set 和 map 的结构1.1 set的结构1.2 map的结构二、对红黑树的改造2.1 改造红黑树的节点2.2 改造红黑树2.2.1 仿函数的使用2.2.2 插入函数的改造2.2.3 删除函…

根据ASTM D4169-23e1标准,如何选择合适的流通周期进行测试?

根据ASTM D4169-23e1标准及行业实践&#xff0c;选择流通周期&#xff08;DC&#xff09;需综合以下因素&#xff1a;一、核心选择依据‌产品属性与包装形式‌‌重量体积‌&#xff1a;轻小包裹&#xff08;<4.53kg且<0.056m&#xff09;适用DC2/3/4/6/9/13-17等周期&…

MySQL的触发器:

目录 触发器的概念&#xff1a; 创建触发器&#xff1a; 查看触发器&#xff1a; 查看当前数据库的所有触发器的定义&#xff1a; 查看当前数据中某个触发器的定义&#xff1a; 从系统information_schema的TRIGGERS表中查询"salary_check_trigger"触发器的信息…

基于ubuntu搭建gitlab

原文地址&#xff1a;基于ubuntu搭建gitlab – 无敌牛 欢迎参观我的网站&#xff1a;无敌牛 – 技术/著作/典籍/分享等 之前介绍了一个使用 git openssh-server 搭建一个极简 git 库的方法&#xff0c;感兴趣可以查看往期文章&#xff1a;手搓一个极简远端git库 – 无敌牛 。…

测试GO前沿实验室:为水系电池研究提供多维度表征解决方案

测试GO前沿实验室&#xff1a;为水系电池研究提供多维度表征解决方案随着全球能源转型加速&#xff0c;水系电池因其高安全性、低成本和环境友好特性&#xff0c;成为下一代储能技术的重要发展方向。测试狗前沿实验室针对水系电池研发中的关键科学问题&#xff0c;整合先进表征…

Spring Boot 中 YAML 配置文件详解

Spring Boot 中 YAML 配置文件详解 在 Spring Boot 项目中&#xff0c;配置文件是不可或缺的一部分&#xff0c;用于自定义应用行为、覆盖默认设置。除了传统的 properties 文件&#xff0c;Spring Boot 对 YAML&#xff08;YAML Ain’t Markup Language&#xff09;格式提供了…

Milvus安装可视化工具,attu,保姆级

安装包链接&#xff1a;GitHub - zilliztech/attu: Web UI for Milvus Vector Databasehttps://github.com/zilliztech/attu?tabreadme-ov-file 下滑 举例&#xff1a;windows&#xff1a;下载安装&#xff0c;然后就可以连接了&#xff08;安装完打开后如果需要输入用户名密码…

避免“卡脖子”!如何减少内存I/O延迟对程序的影响?

单来说&#xff0c;内存 IO 就像是计算机的 “数据高速公路”&#xff0c;负责在内存和其他设备&#xff08;如硬盘、CPU 等&#xff09;之间传输数据。它的速度和效率直接影响着计算机系统的整体性能。 你有没有想过&#xff0c;当你点击电脑上的一个应用程序&#xff0c;它是…

V4L2摄像头采集 + WiFi实时传输实战全流程

&#x1f4d6; 推荐阅读&#xff1a;《Yocto项目实战教程:高效定制嵌入式Linux系统》 &#x1f3a5; 更多学习视频请关注 B 站&#xff1a;嵌入式Jerry V4L2摄像头采集 WiFi实时传输实战全流程 1. 实战场景概述 目标&#xff1a; 嵌入式设备&#xff08;如RK3588/正点原子开发…

Java 之 设计模式

1.单例模式1. ​​饿汉式&#xff08;Eager Initialization&#xff09;​​​​核心原理​​&#xff1a;类加载时立即创建实例&#xff0c;通过静态变量直接初始化。​​代码示例​​&#xff1a;public class Singleton {private static final Singleton INSTANCE new Sing…

[激光原理与应用-185]:光学器件 - BBO、LBO、CLBO晶体的全面比较

一、相同点非线性光学晶体属性BBO、LBO、CLBO均为非中心对称晶体&#xff0c;具备非线性光学效应&#xff0c;广泛应用于激光频率转换&#xff08;如倍频、三倍频、和频、差频&#xff09;、光学参量振荡&#xff08;OPO&#xff09;及电光调制等领域。宽透光范围三者均覆盖紫外…

Android APN加载耗时优化可行性分析

背景 根据Android系统底层机制和行业实践,本文讨论 APN 加载耗时从4.2s降至0.8s的数据合理性和技术可行性,需结合具体优化手段和硬件环境综合分析。 以下是关键判断依据及行业参考: ⚙️ 一、APN加载耗时基准参考 未优化场景的典型耗时 首次开机或重置后:APN需从apns-con…

mysql进阶-sql调优

概述优化索引在MySQL初阶的课程中已经介绍了索引&#xff0c;我们知道InnoDB存储引擎使⽤B树作为索引默认的数据结构来组织数据&#xff0c;为频繁查询的列建⽴索引可以有效的提升查询效率&#xff0c;那么如何利⽤索引编写出⾼效的SQL查询语句&#xff1f;以及如何分析某个查询…

海量数据处理问题详解

1.从a&#xff0c;b两个文件各存放50亿个url&#xff08;每个url大小为64B&#xff09;&#xff0c;如何在内存为4G中查找a&#xff0c;b中相同的url 计算各文件存放大小&#xff1a;50亿*64B 大约为320G&#xff0c;而内存只有4G&#xff0c;显然存放不下&#xff0c;此时我们…

AI 记忆管理系统:工程实现设计方案

本文档为《从“健忘”到“懂我”&#xff1a;构建新一代AI记忆系统》中所述理念的详细工程实现方案。它将聚焦于技术选型、模块设计、数据流转和核心算法&#xff0c;为开发团队提供清晰的落地指引。 1. 系统架构与技术选型 为实现分层记忆与读写分离的设计理念&#xff0c;我们…

Linux驱动学习day26天(RS485)

一、原理通过芯片将232信号转换成485信号&#xff0c;485表示0和1的方法&#xff1a;Va - Vb 的电压差在2~6V时表示1&#xff0c;Va - Vb 的电压差在-2~-6V时表示0。这样传输不容易受到干扰&#xff0c;并且传输距离长。我们需要做的事情就是发送&#xff1a;使能DE(driver ena…

从零构建TransformerP1-了解设计

欢迎来到啾啾的博客&#x1f431;。 记录学习点滴。分享工作思考和实用技巧&#xff0c;偶尔也分享一些杂谈&#x1f4ac;。 有很多很多不足的地方&#xff0c;欢迎评论交流&#xff0c;感谢您的阅读和评论&#x1f604;。 目录引言1 概念回顾1.1 序列任务1.1.1 将序列变成模型…

JVM 终止机制详解:用户线程与守护线程

用户线程未执行完是否会阻止 JVM 终止&#xff1f;答案是&#xff1a;取决于线程类型。让我详细解释&#xff1a; 核心规则 #mermaid-svg-bg5xpyMAeRWNGGk2 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-bg5xpyMAe…

Linux Vim 常用快捷键

Vim中最常用的快捷键&#xff0c;熟练掌握它们可以大大提高编辑效率。移动光标h- 左移j- 下移k- 上移l- 右移w- 移动到下一个单词开头b- 移动到上一个单词开头e- 移动到单词末尾0- 移动到行首$- 移动到行尾gg- 移动到文件开头G- 移动到文件末尾:n- 跳转到第n行插入模式i- 在光标…

【Bellman负环】Cycle Finding

题目翻译给定一个有向图&#xff0c;你的任务是判断它是否包含负环&#xff0c;并给出这样一个环的示例。输入 第一行输入两个整数 n 和 m&#xff1a;分别表示节点数和边数。节点编号为 1, 2, ..., n。 接下来 m 行描述边&#xff0c;每行有三个整数 a, b, c&#xff1a;表示存…