【華為機試】63. 不同路徑 II

文章目錄

  • 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;表示存…