检查网格中是否存在有效路径
难度:
标签:
题目描述
给你一个 m x n 的网格 grid
。网格里的每个单元都代表一条街道。grid[i][j]
的街道可以是:
- 1 表示连接左单元格和右单元格的街道。
- 2 表示连接上单元格和下单元格的街道。
- 3 表示连接左单元格和下单元格的街道。
- 4 表示连接右单元格和下单元格的街道。
- 5 表示连接左单元格和上单元格的街道。
- 6 表示连接右单元格和上单元格的街道。
你最开始从左上角的单元格 (0,0)
开始出发,网格中的「有效路径」是指从左上方的单元格 (0,0)
开始、一直到右下方的 (m-1,n-1)
结束的路径。该路径必须只沿着街道走。
注意:你 不能 变更街道。
如果网格中存在有效的路径,则返回 true
,否则返回 false
。
示例 1:
输入:grid = [[2,4,3],[6,5,2]] 输出:true 解释:如图所示,你可以从 (0, 0) 开始,访问网格中的所有单元格并到达 (m - 1, n - 1) 。
示例 2:
输入:grid = [[1,2,1],[1,2,1]] 输出:false 解释:如图所示,单元格 (0, 0) 上的街道没有与任何其他单元格上的街道相连,你只会停在 (0, 0) 处。
示例 3:
输入:grid = [[1,1,2]] 输出:false 解释:你会停在 (0, 1),而且无法到达 (0, 2) 。
示例 4:
输入:grid = [[1,1,1,1,1,1,3]] 输出:true
示例 5:
输入:grid = [[2],[2],[2],[2],[2],[2],[6]] 输出:true
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
1 <= grid[i][j] <= 6
代码结果
运行时间: 92 ms, 内存: 20.2 MB
/*
* 思路:
* 使用流式处理(Stream)可以简化一些操作,但核心思路与基本的DFS一致。
* 我们依然从起点开始,根据街道类型决定下一步移动的方向,并检查是否到达终点。
*/
import java.util.*;
import java.util.stream.*;
public class SolutionStream {
private int[][] directions = {
{}, // placeholder for index 0
{0, -1, 0, 1}, // type 1: left-right
{-1, 0, 1, 0}, // type 2: up-down
{0, -1, 1, 0}, // type 3: left-down
{0, 1, 1, 0}, // type 4: right-down
{0, -1, -1, 0}, // type 5: left-up
{0, 1, -1, 0} // type 6: right-up
};
private boolean[][] visited;
public boolean hasValidPath(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
visited = new boolean[m][n];
return dfs(grid, 0, 0);
}
private boolean dfs(int[][] grid, int x, int y) {
int m = grid.length;
int n = grid[0].length;
if (x == m - 1 && y == n - 1) return true;
visited[x][y] = true;
int type = grid[x][y];
return IntStream.range(0, directions[type].length / 2)
.mapToObj(i -> new int[] {x + directions[type][i * 2], y + directions[type][i * 2 + 1]})
.filter(pos -> pos[0] >= 0 && pos[0] < m && pos[1] >= 0 && pos[1] < n && !visited[pos[0]][pos[1]])
.anyMatch(pos -> isValidMove(type, grid[pos[0]][pos[1]], directions[type][0], directions[type][1]) && dfs(grid, pos[0], pos[1]));
}
private boolean isValidMove(int type, int nextType, int dx, int dy) {
return IntStream.range(0, directions[nextType].length / 2)
.anyMatch(i -> directions[nextType][i * 2] == -dx && directions[nextType][i * 2 + 1] == -dy);
}
}
解释
方法:
这个题解采用的是基于方向的模拟遍历方法。首先定义了四个基本的前进方向和一个映射表mp。映射表mp用于指示每个类型的街道如何改变当前的方向。我们从网格的左上角开始,尝试沿每个可能的初始方向进行遍历。在遍历的过程中,我们检查是否越界或者是否重复访问同一个单元格,若是,则此路径无效。如果到达右下角,则返回true表示存在有效路径。如果所有的可能初始方向都无法到达目的地,则返回false。
时间复杂度:
O(m*n)
空间复杂度:
O(m*n)
代码细节讲解
🦆
映射表`mp`中的-1是如何作用的?它在算法中代表什么意义?
▷🦆
为什么在`travel`函数中,每次遍历之前不重置`vis`数组的状态,而是只在开始时将`vis[0][0]`设置为False?这是否会影响算法的正确执行?
▷🦆
在`travel`函数的实现中,是否有考虑到街道的连接性质,即确保从一个单元格移动到另一个单元格时,两个单元格的街道是相互连接的?
▷🦆
算法中提到从四个基本方向开始尝试,这四个方向是如何确定的,它们在具体实现中如何发挥作用?
▷