英文原文
You are given an m x n
integer array grid
where grid[i][j]
could be:
1
representing the starting square. There is exactly one starting square.2
representing the ending square. There is exactly one ending square.0
representing empty squares we can walk over.-1
representing obstacles that we cannot walk over.
Return the number of 4-directional walks from the starting square to the ending square, that walk over every non-obstacle square exactly once.
Example 1:
Input: grid = [[1,0,0,0],[0,0,0,0],[0,0,2,-1]] Output: 2 Explanation: We have the following two paths: 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)
Example 2:
Input: grid = [[1,0,0,0],[0,0,0,0],[0,0,0,2]] Output: 4 Explanation: We have the following four paths: 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)
Example 3:
Input: grid = [[0,1],[2,0]] Output: 0 Explanation: There is no path that walks over every empty square exactly once. Note that the starting and ending square can be anywhere in the grid.
Constraints:
m == grid.length
n == grid[i].length
1 <= m, n <= 20
1 <= m * n <= 20
-1 <= grid[i][j] <= 2
- There is exactly one starting cell and one ending cell.
中文题目
在二维网格 grid
上,有 4 种类型的方格:
1
表示起始方格。且只有一个起始方格。2
表示结束方格,且只有一个结束方格。0
表示我们可以走过的空方格。-1
表示我们无法跨越的障碍。
返回在四个方向(上、下、左、右)上行走时,从起始方格到结束方格的不同路径的数目。
每一个无障碍方格都要通过一次,但是一条路径中不能重复通过同一个方格。
示例 1:
输入:[[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:
输入:[[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:
输入:[[0,1],[2,0]] 输出:0 解释: 没有一条路能完全穿过每一个空的方格一次。 请注意,起始和结束方格可以位于网格中的任意位置。
提示:
1 <= grid.length * grid[0].length <= 20
通过代码
官方题解
方法一:回溯深度优先搜索
思路与算法
让我们尝试遍历每一个 0
方格,并在走过的方格里留下一个障碍。回溯的时候,我们要删除那些自己留下的障碍。
介于输入数据的限制,这个方法是可以通过的,因为一个不好的路径很快就会因没有无障碍的方格可以走而被卡住。
class Solution {
int ans;
int[][] grid;
int tr, tc;
int[] dr = new int[]{0, -1, 0, 1};
int[] dc = new int[]{1, 0, -1, 0};
int R, C;
public int uniquePathsIII(int[][] grid) {
this.grid = grid;
R = grid.length;
C = grid[0].length;
int todo = 0;
int sr = 0, sc = 0;
for (int r = 0; r < R; ++r)
for (int c = 0; c < C; ++c) {
if (grid[r][c] != -1) {
todo++;
}
if (grid[r][c] == 1) {
sr = r;
sc = c;
} else if (grid[r][c] == 2) {
tr = r;
tc = c;
}
}
ans = 0;
dfs(sr, sc, todo);
return ans;
}
public void dfs(int r, int c, int todo) {
todo--;
if (todo < 0) return;
if (r == tr && c == tc) {
if (todo == 0) ans++;
return;
}
grid[r][c] = 3;
for (int k = 0; k < 4; ++k) {
int nr = r + dr[k];
int nc = c + dc[k];
if (0 <= nr && nr < R && 0 <= nc && nc < C) {
if (grid[nr][nc] % 2 == 0)
dfs(nr, nc, todo);
}
}
grid[r][c] = 0;
}
}
class Solution:
def uniquePathsIII(self, grid):
R, C = len(grid), len(grid[0])
def nei***ors(r, c):
for nr, nc in ((r-1, c), (r, c-1), (r+1, c), (r, c+1)):
if 0 <= nr < R and 0 <= nc < C and grid[nr][nc] % 2 == 0:
yield nr, nc
todo = 0
for r, row in enumerate(grid):
for c, val in enumerate(row):
if val != -1: todo += 1
if val == 1: sr, sc = r, c
if val == 2: tr, tc = r, c
self.ans = 0
def dfs(r, c, todo):
todo -= 1
if todo < 0: return
if r == tr and c == tc:
if todo == 0:
self.ans += 1
return
grid[r][c] = -1
for nr, nc in nei***ors(r, c):
dfs(nr, nc, todo)
grid[r][c] = 0
dfs(sr, sc, todo)
return self.ans
复杂度分析
时间复杂度:$O(4^{R*C})$,其中 $R, C$ 是这个二维网格行与列的大小。(我们可以找到一个更加精确的界限,但是这个界限已经超越了本文的范围)
空间复杂度:$O(R*C)$。
方法二:动态规划
思路与算法
让我们定义 dp(r, c, todo)
为从 (r, c)
开始行走,还没有遍历的无障碍方格集合为 todo
的好路径的数量。
我们可以使用一个与 方法一 类似的方法,并通过记忆化状态 (r, c, todo)
的答案来避免重复搜索。
class Solution {
int ans;
int[][] grid;
int R, C;
int tr, tc, target;
int[] dr = new int[]{0, -1, 0, 1};
int[] dc = new int[]{1, 0, -1, 0};
Integer[][][] memo;
public int uniquePathsIII(int[][] grid) {
this.grid = grid;
R = grid.length;
C = grid[0].length;
target = 0;
int sr = 0, sc = 0;
for (int r = 0; r < R; ++r)
for (int c = 0; c < C; ++c) {
if (grid[r][c] % 2 == 0)
target |= code(r, c);
if (grid[r][c] == 1) {
sr = r;
sc = c;
} else if (grid[r][c] == 2) {
tr = r;
tc = c;
}
}
memo = new Integer[R][C][1 << R*C];
return dp(sr, sc, target);
}
public int code(int r, int c) {
return 1 << (r * C + c);
}
public Integer dp(int r, int c, int todo) {
if (memo[r][c][todo] != null)
return memo[r][c][todo];
if (r == tr && c == tc) {
return todo == 0 ? 1 : 0;
}
int ans = 0;
for (int k = 0; k < 4; ++k) {
int nr = r + dr[k];
int nc = c + dc[k];
if (0 <= nr && nr < R && 0 <= nc && nc < C) {
if ((todo & code(nr, nc)) != 0)
ans += dp(nr, nc, todo ^ code(nr, nc));
}
}
memo[r][c][todo] = ans;
return ans;
}
}
from functools import lru_cache
class Solution:
def uniquePathsIII(self, grid):
R, C = len(grid), len(grid[0])
def code(r, c):
return 1 << (r * C + c)
def nei***ors(r, c):
for nr, nc in ((r-1, c), (r, c-1), (r+1, c), (r, c+1)):
if 0 <= nr < R and 0 <= nc < C and grid[nr][nc] % 2 == 0:
yield nr, nc
target = 0
for r, row in enumerate(grid):
for c, val in enumerate(row):
if val % 2 == 0:
target |= code(r, c)
if val == 1:
sr, sc = r, c
if val == 2:
tr, tc = r, c
@lru_cache(None)
def dp(r, c, todo):
if r == tr and c == tc:
return +(todo == 0)
ans = 0
for nr, nc in nei***ors(r, c):
if todo & code(nr, nc):
ans += dp(nr, nc, todo ^ code(nr, nc))
return ans
return dp(sr, sc, target)
复杂度分析
- 时间复杂度:$O(R * C * 2^{R*C})$,其中 $R, C$ 是给定二维网格行与列的大小。
- 空间复杂度:$O(R * C * 2^{R*C})$。
统计信息
通过次数 | 提交次数 | AC比率 |
---|---|---|
14932 | 20279 | 73.6% |
提交历史
提交时间 | 提交结果 | 执行时间 | 内存消耗 | 语言 |
---|
相似题目
题目 | 难度 |
---|---|
解数独 | 困难 |
不同路径 II | 中等 |
单词搜索 II | 困难 |