1. 题目
给你一个 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
2. 分析
这道题主要考察的是思路,而非性能。每种街道类型其实对应两种走法,比如Street1,虽然是从左到右的一条直道,但也可以是从右到左的走法,你很能想到测试用例里面一定有这种弯弯绕绕的路线等着咱们。
另一点需要注意的是,虽然起点是(0,0),但并不需要起点的入口是上方或者是左方,可以是任一一方,那么,从起点开始,就需要判断这个单元格的两个街道方向,是否有可能走到目的终点。下图的测试用例即为实际需要返回为true的案例。
基于这些分析,我的思路是把所有街道类型的走法给穷举出来,然后从起点开始,按照这个街道的两个通道方向往下找,主要计算下一个单元格的位置,判断是否超出界限,是否连通即可。
3. 代码实现
class Solution {
// 入口和出口的朝向 0-up 1-right 2-down 3-left
// 入口是当前单元格的入口,出口则是下一个单元格的入口
private static final int[][][] path = new int[][][]{
{{1, 1}, {3, 3}},
{{0, 0}, {2, 2}},
{{1, 0}, {2, 3}},
{{2, 1}, {3, 0}},
{{1, 2}, {0, 3}},
{{0, 1}, {3, 2}}
};
private static final int[][][] next = new int[][][]{
{{0, 1}, {0, -1}},
{{1, 0}, {-1, 0}},
{{1, 0}, {0, -1}},
{{0, 1}, {1, 0}},
{{-1, 0}, {0, -1}},
{{0, 1}, {-1, 0}}
};
public boolean hasValidPath(int[][] grid) {
if (grid.length == 1 && grid[0].length == 1) {
return true;
}
int index = grid[0][0] - 1;
// 首个位置需特殊处理
int[][] firstPath = path[index];
// 方向1
int[][] nextCord = next[index];
if (find(grid, nextCord[0][0], nextCord[0][1], firstPath[0][1])) {
return true;
}
// 方向2
return find(grid, nextCord[1][0], nextCord[1][1], firstPath[1][1]);
}
private static boolean find(int[][] grid, int x, int y, int lastDirection) {
while (true) {
// 是否走出边界外
if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length) {
return false;
}
int index = grid[x][y] - 1;
boolean flag;
int[][] ints = path[index];
if (x == 0 && y == 0) {
// 又回到了起点
return false;
}
// 判断连通性
if (ints[0][0] == lastDirection) {
flag = true;
} else if (ints[1][0] == lastDirection) {
flag = false;
} else {
// 道路不通
return false;
}
if (x == grid.length - 1 && y == grid[0].length - 1) {
// 走到终点
return true;
}
// 获取下一个位置
int[] cord = next[index][flag ? 0 : 1];
x += cord[0];
y += cord[1];
// 当前位置的出口方向
lastDirection = flag ? ints[0][1] : ints[1][1];
}
}
}
4. 总结
补充说明一下path,通过三维数组定义每种街道的两种通道类型。
以这个Street3为例,如果是左上角进入,那么这个单元格的入口类型为1,对应的出口类型为2,但是,在判断连通性的时候,是用当前的出口与下个单元格的入口类型进行匹配,所以,此处我直接将这个出口类型2转换为了入口类型0,所以(1,0)是这么来的。换个放下,若成下方进,则入口类型为2,出口类型为1,再将出口类型转换为下个单元格的入口类型3,所以有了(2,3)。完整地,Street3的path数据即为
{{1, 0}, {2, 3}}