Hard
题目描述
给你一个 m x n 的整数数组 grid,其中 grid[i][j] 可能为:
1表示起始方格。有且只有一个起始方格。2表示结束方格。有且只有一个结束方格。0表示我们可以走过的空方格。-1表示我们无法跨越的障碍物。
返回在四个方向(上、下、左、右)上行走从起始方格到结束方格的不同路径数,每一个无障碍方格都要通过一次。
示例 1:
输入:grid = [[1,0,0,0],[0,0,0,0],[0,0,2,-1]]
输出:2
解释:我们有以下两条路径:
1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2)
2. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2)
示例 2:
输入:grid = [[1,0,0,0],[0,0,0,0],[0,0,0,2]]
输出:4
解释:我们有以下四条路径:
1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2),(2,3)
2. (0,0),(0,1),(1,1),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,3),(1,3),(2,3)
3. (0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(1,1),(0,1),(0,2),(0,3),(1,3),(2,3)
4. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2),(2,3)
示例 3:
输入:grid = [[0,1],[2,0]]
输出:0
解释:没有一条路径能够使我们走过每一个空方格一次。
请注意,起始和结束方格可以位于网格中的任意位置。
提示:
m == grid.lengthn == grid[i].length1 <= m, n <= 201 <= m * n <= 20-1 <= grid[i][j] <= 2- 网格中只有一个起始单元格和一个结束单元格。
解题思路
这道题要求从起始点到终点的路径,且必须经过每一个非障碍方格恰好一次。这是一个典型的回溯问题。
解题思路:
预处理阶段:遍历网格,找到起始位置、终点位置,并统计需要访问的空格子总数(包括起始点和终点)。
回溯算法:从起始位置开始进行深度优先搜索:
- 标记当前位置为已访问(设为-1,表示障碍)
- 向四个方向尝试移动
- 如果到达终点且访问了所有必须访问的格子,则找到一条有效路径
- 回溯时恢复当前位置的原始值
剪枝优化:
- 边界检查:确保不越界
- 障碍检查:不能走到-1的格子
- 已访问检查:不能重复访问同一格子
- 路径长度检查:只有当访问的格子数等于总的非障碍格子数时才能到达终点
推荐解法:回溯 + DFS
时间复杂度相对较高,但由于题目限制网格大小不超过20,完全可以接受。这种解法思路清晰,易于理解和实现。
代码实现
class Solution {
public:
int uniquePathsIII(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
int startX = -1, startY = -1, endX = -1, endY = -1;
int emptyCount = 0;
// 找到起点、终点,统计需要访问的格子数
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
startX = i;
startY = j;
emptyCount++;
} else if (grid[i][j] == 2) {
endX = i;
endY = j;
emptyCount++;
} else if (grid[i][j] == 0) {
emptyCount++;
}
}
}
return dfs(grid, startX, startY, endX, endY, emptyCount);
}
private:
int dfs(vector<vector<int>>& grid, int x, int y, int endX, int endY, int remaining) {
if (x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size() || grid[x][y] == -1) {
return 0;
}
if (x == endX && y == endY) {
return remaining == 1 ? 1 : 0;
}
int original = grid[x][y];
grid[x][y] = -1; // 标记为已访问
int paths = 0;
vector<pair<int, int>> directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
for (auto& dir : directions) {
int newX = x + dir.first;
int newY = y + dir.second;
paths += dfs(grid, newX, newY, endX, endY, remaining - 1);
}
grid[x][y] = original; // 回溯
return paths;
}
};
class Solution:
def uniquePathsIII(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
start_x = start_y = end_x = end_y = -1
empty_count = 0
# 找到起点、终点,统计需要访问的格子数
for i in range(m):
for j in range(n):
if grid[i][j] == 1:
start_x, start_y = i, j
empty_count += 1
elif grid[i][j] == 2:
end_x, end_y = i, j
empty_count += 1
elif grid[i][j] == 0:
empty_count += 1
def dfs(x, y, remaining):
if x < 0 or x >= m or y < 0 or y >= n or grid[x][y] == -1:
return 0
if x == end_x and y == end_y:
return 1 if remaining == 1 else 0
original = grid[x][y]
grid[x][y] = -1 # 标记为已访问
paths = 0
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_x, new_y = x + dx, y + dy
paths += dfs(new_x, new_y, remaining - 1)
grid[x][y] = original # 回溯
return paths
return dfs(start_x, start_y, empty_count)
public class Solution {
public int UniquePathsIII(int[][] grid) {
int m = grid.Length, n = grid[0].Length;
int startX = -1, startY = -1, endX = -1, endY = -1;
int emptyCount = 0;
// 找到起点、终点,统计需要访问的格子数
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
startX = i;
startY = j;
emptyCount++;
} else if (grid[i][j] == 2) {
endX = i;
endY = j;
emptyCount++;
} else if (grid[i][j] == 0) {
emptyCount++;
}
}
}
return DFS(grid, startX, startY, endX, endY, emptyCount);
}
private int DFS(int[][] grid, int x, int y, int endX, int endY, int remaining) {
if (x < 0 || x >= grid.Length || y < 0 || y >= grid[0].Length || grid[x][y] == -1) {
return 0;
}
if (x == endX && y == endY) {
return remaining == 1 ? 1 : 0;
}
int original = grid[x][y];
grid[x][y] = -1; // 标记为已访问
int paths = 0;
int[,] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
for (int i = 0; i < 4; i++) {
int newX = x + directions[i, 0];
int newY = y + directions[i, 1];
paths += DFS(grid, newX, newY, endX, endY, remaining - 1);
}
grid[x][y] = original; // 回溯
return paths;
}
}
var uniquePathsIII = function(grid) {
let startRow = 0, startCol = 0, emptySquares = 0;
const m = grid.length, n = grid[0].length;
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (grid[i][j] === 1) {
startRow = i;
startCol = j;
emptySquares++;
} else if (grid[i][j] === 0) {
emptySquares++;
}
}
}
const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];
function dfs(row, col, remaining) {
if (row < 0 || row >= m || col < 0 || col >= n || grid[row][col] === -1) {
return 0;
}
if (grid[row][col] === 2) {
return remaining === 0 ? 1 : 0;
}
const original = grid[row][col];
grid[row][col] = -1;
let paths = 0;
for (const [dr, dc] of directions) {
paths += dfs(row + dr, col + dc, remaining - 1);
}
grid[row][col] = original;
return paths;
}
return dfs(startRow, startCol, emptySquares);
};
复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 回溯搜索 | O(4^(m×n)) | O(m×n) |
说明:
- 时间复杂度:最坏情况下需要探索所有可能的路径,每个位置最多有4个方向,总共有m×n个位置
- 空间复杂度:递归调用栈的深度最多为m×n(访问所有格子),原地修改grid数组不算额外空间
相关题目
. Sudoku Solver (Hard)
. Unique Paths II (Medium)
. Word Search II (Hard)