不同路径 II
描述
-
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。 -
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
-
网格中的障碍物和空位置分别用 1 和 0 来表示。
示例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
Typescript 版算法实现
1 )方案 1
function uniquePathsWithObstacles(obstacleGrid: number[][]): number {
// 1. 获取当前矩阵的行和列
const m = obstacleGrid.length // 行
const n = obstacleGrid[0].length // 列
// 2. 准备好最终的数据结构:初始化一个m x n的矩阵, 使用矩阵来计算
const matrix: number[][] = new Array(m).fill(0).map(_ => new Array(n).fill(0));
// 3. 遍历第一列,根据给定矩阵,初始化结果矩阵中第一列的值
for (let i = 0; i < m && obstacleGrid[i][0] === 0; i++) {
matrix[i][0] = 1
}
// 4. 遍历第一行,根据给定矩阵,初始化结果矩阵中第一行的值
for (let i = 0; i < n && obstacleGrid[0][i] === 0; i++) {
matrix[0][i] = 1
}
// 遍历原始矩阵这个二维数组,因为第一列和第一行已经遍历过了
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
// 中间有障碍物,直接跳出当前
if (obstacleGrid[i][j] === 1) continue
// 分解后的问题求解公式,
matrix[i][j] = matrix[i - 1][j] + matrix[i][j - 1]
}
}
// 最后一步存储的值
return matrix[m - 1][n - 1]
}
- 这个是官方示例,逆向思维,原矩阵中1是障碍物,0是可移动的位置
- 为了方便计算, 我们通过一个新的矩阵来累加计算,反过来,可移动的为1,障碍物为0
- 因为可移动的位置参与计算,障碍物不参与计算
- 核心公式是:F(m*n) = F((m-1) * n) + F(m * (n - 1))
- 动态规划就是从基础开始计算,直到逐步接近我们最终的目标,也就是最后的一个位置 matrix[m - 1][n - 1]
- 我一开始没想到怎么做,看了官方示例,瞬间就明白了
2 )方案2
function uniquePathsWithObstacles(obstacleGrid: number[][]): number {
const n = obstacleGrid.length; // 行
const m = obstacleGrid[0].length; // 列
// 只需要知道前一列的路径数即可, 大幅减少 空间复杂度
const f: number[] = new Array(m).fill(0); // 用一个 一维数组来存储路径数量, 这是一个滚动数组
f[0] = obstacleGrid[0][0] === 0 ? 1 : 0; // 起始位置的记录
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
if (obstacleGrid[i][j] === 1) {
f[j] = 0;
continue;
}
if (j - 1 >= 0 && obstacleGrid[i][j - 1] === 0) {
f[j] += f[j - 1]; // // 当前格 为 左边一格f[j-1]和上边一格f[j](这里是自身之前)所决定
}
}
}
return f[m - 1]; // 返回一维数组的最后一个
}
- 这里降低了空间复杂度,使用了滚动数组来处理的
Python3 版算法实现
1 ) 方案1
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
# 1. 获取当前矩阵的行和列
m = len(obstacleGrid) # 行
n = len(obstacleGrid[0]) # 列
# 2. 准备好最终的数据结构:初始化一个m x n的矩阵, 使用矩阵来计算
matrix = [[0] * n for _ in range(m)]
# 3. 遍历第一列,根据给定矩阵,初始化结果矩阵中第一列的值
for i in range(m):
if obstacleGrid[i][0] == 0:
matrix[i][0] = 1
else:
break # 如果遇到障碍物,则后续的行都不可达
# 4. 遍历第一行,根据给定矩阵,初始化结果矩阵中第一行的值
for j in range(n):
if obstacleGrid[0][j] == 0:
matrix[0][j] = 1
else:
break # 如果遇到障碍物,则后续的列都不可达
# 遍历原始矩阵这个二维数组,因为第一列和第一行已经遍历过了
for i in range(1, m):
for j in range(1, n):
# 中间有障碍物,直接跳出当前
if obstacleGrid[i][j] == 1:
continue
# 分解后的问题求解公式
matrix[i][j] = matrix[i - 1][j] + matrix[i][j - 1]
# 最后一步存储的值
return matrix[m - 1][n - 1]
2 ) 方案2
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
n = len(obstacleGrid) # 行数
m = len(obstacleGrid[0]) # 列数
# 使用一个一维数组来存储到达每一列底部的路径数量,这是一个滚动数组
f = [0] * m
# 起始位置的记录
f[0] = 1 if obstacleGrid[0][0] == 0 else 0
for i in range(n):
for j in range(m):
# 如果当前位置有障碍,则无法到达该位置,将路径数量设为0
if obstacleGrid[i][j] == 1:
f[j] = 0
continue
# 如果当前位置没有障碍,并且其左侧位置也没有障碍
# 则当前位置的路径数量等于左侧位置的路径数量(因为可以从左侧到达当前位置)
# 注意:这里没有显式地加上来自上方的路径数量,因为f[j]在上一轮外层循环中已经被更新为上一行的结果了
# 由于我们是按行遍历的,所以f[j]在更新时自动地“知道”了上一行的路径数量(如果上方位置无障碍)
if j - 1 >= 0 and obstacleGrid[i][j - 1] == 0:
f[j] += f[j - 1]
# 返回到达网格右下角的路径数量
return f[m - 1]
Golang 版算法实现
1 ) 方案1
func uniquePathsWithObstacles(obstacleGrid [][]int) int {
// 1. 获取当前矩阵的行和列
m := len(obstacleGrid)
if m == 0 {
return 0
}
n := len(obstacleGrid[0])
// 2. 准备好最终的数据结构:初始化一个m x n的矩阵, 使用矩阵来计算
matrix := make([][]int, m)
for i := 0; i < m; i++ {
matrix[i] = make([]int, n)
}
// 3. 遍历第一列,根据给定矩阵,初始化结果矩阵中第一列的值
for i := 0; i < m; i++ {
if obstacleGrid[i][0] == 1 {
break
}
matrix[i][0] = 1
}
// 4. 遍历第一行,根据给定矩阵,初始化结果矩阵中第一行的值
for j := 0; j < n; j++ {
if obstacleGrid[0][j] == 1 {
break
}
matrix[0][j] = 1
}
// 遍历原始矩阵这个二维数组,因为第一列和第一行已经遍历过了
for i := 1; i < m; i++ {
for j := 1; j < n; j++ {
// 中间有障碍物,直接跳出当前
if obstacleGrid[i][j] == 1 {
continue
}
// 分解后的问题求解公式
matrix[i][j] = matrix[i-1][j] + matrix[i][j-1]
}
}
// 最后一步存储的值
return matrix[m-1][n-1]
}
2 )方案2
func uniquePathsWithObstacles(obstacleGrid [][]int) int {
n := len(obstacleGrid) // 行数
if n == 0 {
return 0
}
m := len(obstacleGrid[0]) // 列数
// 使用一个一维数组来存储到达每一列底部的路径数量,这是一个滚动数组
f := make([]int, m)
// 起始位置的记录
if obstacleGrid[0][0] == 0 {
f[0] = 1
}
for i := 0; i < n; i++ {
for j := 0; j < m; j++ {
// 如果当前位置有障碍,则无法到达该位置,将路径数量设为0
if obstacleGrid[i][j] == 1 {
f[j] = 0
continue
}
// 如果当前位置没有障碍,并且其左侧位置也没有障碍
// 则当前位置的路径数量等于左侧位置的路径数量(因为可以从左侧到达当前位置)
// 注意:这里没有显式地加上来自上方的路径数量,因为f[j]在上一轮外层循环中已经被更新为上一行的结果了
if j-1 >= 0 && obstacleGrid[i][j-1] == 0 {
f[j] += f[j-1]
}
// 如果当前位置是第一列且没有障碍,则它的路径数量应该保持不变(因为第一列只能从上方到达)
// 但由于我们是从上到下、从左到右遍历的,这个条件实际上已经被隐含地处理了
// 即:f[0] 在开始时已经被正确设置,且在后续遍历中不会被左侧的值影响(因为j-1会小于0)
}
}
// 返回到达网格右下角的路径数量
return f[m-1]
}
365



被折叠的 条评论
为什么被折叠?



