Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e277336

Browse files
Merge pull request #765 from SharingSource/ac_oier
✨feat: add 1210、1786、1797、2335、456、629、648
2 parents d010df5 + cc36e27 commit e277336

14 files changed

+1002
-49
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[1210. 穿过迷宫的最少移动次数]()** ,难度为 **困难**
4+
5+
Tag : 「BFS」
6+
7+
8+
9+
你还记得那条风靡全球的贪吃蛇吗?
10+
11+
我们在一个 `n*n` 的网格上构建了新的迷宫地图,蛇的长度为 `2`,也就是说它会占去两个单元格。蛇会从左上角(`(0, 0)``(0, 1)`)开始移动。我们用 `0` 表示空单元格,用 `1` 表示障碍物。蛇需要移动到迷宫的右下角(`(n-1, n-2)``(n-1, n-1)`)。
12+
13+
每次移动,蛇可以这样走:
14+
15+
* 如果没有障碍,则向右移动一个单元格。并仍然保持身体的水平/竖直状态。
16+
* 如果没有障碍,则向下移动一个单元格。并仍然保持身体的水平/竖直状态。
17+
* 如果它处于水平状态并且其下面的两个单元都是空的,就顺时针旋转 `90` 度。蛇从(`(r, c)``(r, c+1)`)移动到 (`(r, c)``(r+1, c)`)。
18+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/28/image-2.png)
19+
* 如果它处于竖直状态并且其右面的两个单元都是空的,就逆时针旋转 `90` 度。蛇从(`(r, c)``(r+1, c)`)移动到(`(r, c)``(r, c+1)`)。
20+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/28/image-1.png)
21+
22+
返回蛇抵达目的地所需的最少移动次数。
23+
24+
如果无法到达目的地,请返回 `-1`
25+
26+
示例 1:
27+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/28/image.png)
28+
```
29+
输入:grid = [[0,0,0,0,0,1],
30+
[1,1,0,0,1,0],
31+
[0,0,0,0,1,1],
32+
[0,0,1,0,1,0],
33+
[0,1,1,0,0,0],
34+
[0,1,1,0,0,0]]
35+
36+
输出:11
37+
38+
解释:
39+
一种可能的解决方案是 [右, 右, 顺时针旋转, 右, 下, 下, 下, 下, 逆时针旋转, 右, 下]。
40+
```
41+
示例 2:
42+
```
43+
输入:grid = [[0,0,1,1,1,1],
44+
[0,0,0,0,1,1],
45+
[1,1,0,0,0,1],
46+
[1,1,1,0,0,1],
47+
[1,1,1,0,0,1],
48+
[1,1,1,0,0,0]]
49+
50+
输出:9
51+
```
52+
53+
提示:
54+
* 2ドル <= n <= 100$
55+
* 0ドル <= grid[i][j] <= 1$
56+
* 蛇保证从空单元格开始出发。
57+
58+
---
59+
60+
### BFS
61+
62+
题目要我们求从特定起点到特定终点的最少步数,由于我们蛇的长度固定为 2ドル,ドル因此我们可用三元组 $(x, y, cd)$ 来代表蛇的实际位置。其中 $(x, y)$ 代表蛇尾位置,$cd$ 代表当前蛇的方向状态,0ドル$ 代表水平状态,1ドル$ 代表竖直状态。
63+
64+
蛇尾加上方向状态可确定其蛇头位置 :`tx = cd == 0 ? nx : nx + 1``ty = cd == 0 ? ny + 1 : ny`
65+
66+
对四种移动规则所导致三元组变化进行分情况讨论:
67+
68+
1. 往右移动:对于蛇尾而言,只有维度 $y$ 进行加一,其余维度不变。三元组变化总结为 $(0, 1, 0)$
69+
2. 往下移动:对于蛇尾而言,只有维度 $x$ 进行加一,其余维度不变。三元组变化总结为 $(1, 0, 0)$
70+
3. 旋转:对于蛇尾,只有 $cd$ 维度对进行翻转,其余维度不变。三元组变化总结定为 $(0, 0, 1)$
71+
72+
综上,所有移动规则可总结为 `int[][] dirs = new int[][]{{1,0,0},{0,1,0},{0,0,1}}`
73+
74+
在进行 `BFS` 时,通过遍历 `dirs` 来得到新的三元组:原位置 `(x, y, cd)` 转换到新位置 `(x + dir[0], y + dir[1], cd ^ dir[2])`
75+
76+
在得到新蛇尾位置 $(nx, ny)$ 之后,计算新蛇头的位置 $(tx, ty)$。需要确保整条蛇没有越界,没有碰到障碍物,并且旋转转移时,额外检查 $(x + 1, y + 1)$ 位置是否合法。
77+
78+
79+
代码:
80+
```Java
81+
class Solution {
82+
int[][] dirs = new int[][]{{1,0,0},{0,1,0},{0,0,1}};
83+
public int minimumMoves(int[][] g) {
84+
int n = g.length;
85+
Deque<int[]> d = new ArrayDeque<>();
86+
d.addLast(new int[]{0,0,0,0});
87+
boolean[][][] vis = new boolean[n][n][2];
88+
vis[0][0][0] = true;
89+
while (!d.isEmpty()) {
90+
int[] info = d.pollFirst();
91+
int x = info[0], y = info[1], cd = info[2], step = info[3];
92+
for (int[] dir : dirs) {
93+
int nx = x + dir[0], ny = y + dir[1], nd = cd ^ dir[2]; // 新蛇尾位置和方向
94+
int tx = nd == 0 ? nx : nx + 1, ty = nd == 0 ? ny + 1 : ny; // 新蛇头
95+
if (nx >= n || ny >= n || tx >= n || ty >= n) continue; // 整条蛇不越界
96+
if (g[nx][ny] == 1 || g[tx][ty] == 1) continue; // 没有触及障碍物
97+
if (vis[nx][ny][nd]) continue;
98+
if (cd != nd && g[x + 1][y + 1] == 1) continue; // 旋转时,额外检查多一个位置
99+
if (nx == n - 1 && ny == n - 2 && nd == 0) return step + 1;
100+
d.addLast(new int[]{nx, ny, nd, step + 1});
101+
vis[nx][ny][nd] = true;
102+
}
103+
}
104+
return -1;
105+
}
106+
}
107+
```
108+
* 时间复杂度:$O(n^2)$
109+
* 空间复杂度:$O(n^2 \times C),ドル其中 $C = 2$ 代表蛇可变状态方向
110+
111+
---
112+
113+
### 最后
114+
115+
这是我们「刷穿 LeetCode」系列文章的第 `No.1210` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
116+
117+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
118+
119+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
120+
121+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
122+
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[1129. 颜色交替的最短路径]()** ,难度为 **中等**
4+
5+
Tag : 「BFS」、「最短路」
6+
7+
8+
9+
在一个有向图中,节点分别标记为 `0, 1, ..., n-1`。图中每条边为红色或者蓝色,且存在自环或平行边。
10+
11+
`red_edges` 中的每一个 `[i, j]` 对表示从节点 `i` 到节点 `j` 的红色有向边。类似地,`blue_edges` 中的每一个 `[i, j]` 对表示从节点 `i` 到节点 `j` 的蓝色有向边。
12+
13+
返回长度为 `n` 的数组 `answer`,其中 `answer[X]` 是从节点 `0` 到节点 `X` 的红色边和蓝色边交替出现的最短路径的长度。如果不存在这样的路径,那么 `answer[x] = -1`
14+
15+
示例 1:
16+
```
17+
输入:n = 3, red_edges = [[0,1],[1,2]], blue_edges = []
18+
19+
输出:[0,1,-1]
20+
```
21+
示例 2:
22+
```
23+
输入:n = 3, red_edges = [[0,1]], blue_edges = [[2,1]]
24+
25+
输出:[0,1,-1]
26+
```
27+
示例 3:
28+
```
29+
输入:n = 3, red_edges = [[1,0]], blue_edges = [[2,1]]
30+
31+
输出:[0,-1,-1]
32+
```
33+
示例 4:
34+
```
35+
输入:n = 3, red_edges = [[0,1]], blue_edges = [[1,2]]
36+
37+
输出:[0,1,2]
38+
```
39+
示例 5:
40+
```
41+
输入:n = 3, red_edges = [[0,1],[0,2]], blue_edges = [[1,0]]
42+
43+
输出:[0,1,1]
44+
```
45+
46+
提示:
47+
* `1 <= n <= 100`
48+
* `red_edges.length <= 400`
49+
* `blue_edges.length <= 400`
50+
* `red_edges[i].length = blue_edges[i].length = 2`
51+
* `0 <= red_edges[i][j], blue_edges[i][j] < n`
52+
53+
---
54+
55+
### 朴素 BFS
56+
57+
为了方便,将 `redEdges` 记为 `rs`,将 `blueEdges` 记为 `bs`
58+
59+
使用两数组进行建图,利用点数和边数关系(稀疏图),使用「邻接表」方式进行建图。
60+
61+
将所有红色有向边权值记为 `1`,将所有蓝色有向边权值记为 `-1`。注意这里的权重仅表示该边的颜色,并非代表经过该边的真实成本。
62+
63+
也正是经过所有边的成本均相同,对于原问题「从 `0` 号节点进行出发,求到所有点的颜色交替的最短路径」,我们容易想到使用 `BFS` 进行求解。
64+
65+
`BFS` 过程中将三元组 $(point, last, step)$ 进行入队:
66+
67+
* `point` 代表当前所在点编号
68+
* `last` 代表到达 `point` 所使用的边的颜色,默认为 `0` 代表不需要经过任何边到达起点
69+
* `step` 代表到达 `point` 所使用的步数
70+
71+
利用数据范围不大(点数为 100ドル,ドル边数为 800ドル$),可以直接对每个点进行一次独立的 `BFS` 来求最短路。由于图存在重边和自环,因此在求解最短路的过程需要对边进行去重。
72+
73+
代码:
74+
```Java
75+
class Solution {
76+
static int N = 110, M = 810, idx = 0;
77+
static int[] he = new int[N], e = new int[M], ne = new int[M], w = new int[M];
78+
void add(int a, int b, int c) {
79+
e[idx] = b;
80+
w[idx] = c;
81+
ne[idx] = he[a];
82+
he[a] = idx++;
83+
}
84+
public int[] shortestAlternatingPaths(int n, int[][] rs, int[][] bs) {
85+
idx = 0;
86+
Arrays.fill(he, -1);
87+
for (int[] e : rs) add(e[0], e[1], 1);
88+
for (int[] e : bs) add(e[0], e[1], -1);
89+
int[] ans = new int[n];
90+
boolean[] vis = new boolean[rs.length + bs.length];
91+
out:for (int k = 1; k < n; k++) {
92+
ans[k] = -1;
93+
Arrays.fill(vis, false);
94+
Deque<int[]> d = new ArrayDeque<>();
95+
d.addLast(new int[]{0, 0, 0}); // point, last, step
96+
while (!d.isEmpty()) {
97+
int[] info = d.pollFirst();
98+
int p = info[0], last = info[1], step = info[2];
99+
for (int i = he[p]; i != -1; i = ne[i]) {
100+
int j = e[i], c = w[i];
101+
if (vis[i]) continue;
102+
if (c + last == 0 || last == 0) {
103+
if (j == k) {
104+
ans[k] = step + 1;
105+
continue out;
106+
} else {
107+
d.addLast(new int[]{j, c, step + 1});
108+
vis[i] = true;
109+
}
110+
}
111+
}
112+
}
113+
}
114+
return ans;
115+
}
116+
}
117+
```
118+
* 时间复杂度:将点数记为 `n` ,边数记为 `m`。建图复杂度为 $O(m)$;构造答案复杂度为 $O(n \times (n + m))$。整体复杂度为 $O(n \times (n + m))$
119+
* 空间复杂度:$O(n + m)$
120+
121+
---
122+
123+
### 优化
124+
125+
实际上,我们并没有对每个点进行独立 `BFS` 的必要。
126+
127+
为了获取所有从节点 `0` 出发的最短路,可直接从节点 `0` 进行出发(对边进行去重),所有能够从节点 `0` 沿交替路径到达的节点必然都会被访问到,且节点首次被访问到时必然是最短路径。
128+
129+
因此我们只需要初始化所有的 `ans[i] = -1`,并从节点 `0` 开始进行一次 `BFS` 即可。
130+
131+
代码:
132+
```Java
133+
class Solution {
134+
static int N = 110, M = 810, idx = 0;
135+
static int[] he = new int[N], e = new int[M], ne = new int[M], w = new int[M];
136+
void add(int a, int b, int c) {
137+
e[idx] = b;
138+
w[idx] = c;
139+
ne[idx] = he[a];
140+
he[a] = idx++;
141+
}
142+
public int[] shortestAlternatingPaths(int n, int[][] rs, int[][] bs) {
143+
idx = 0;
144+
Arrays.fill(he, -1);
145+
for (int[] e : rs) add(e[0], e[1], 1);
146+
for (int[] e : bs) add(e[0], e[1], -1);
147+
int[] ans = new int[n];
148+
Arrays.fill(ans, -1);
149+
ans[0] = 0;
150+
boolean[] vis = new boolean[rs.length + bs.length];
151+
Arrays.fill(vis, false);
152+
Deque<int[]> d = new ArrayDeque<>();
153+
d.addLast(new int[]{0, 0, 0}); // point, last, step
154+
while (!d.isEmpty()) {
155+
int[] info = d.pollFirst();
156+
int p = info[0], last = info[1], step = info[2];
157+
for (int i = he[p]; i != -1; i = ne[i]) {
158+
if (vis[i]) continue;
159+
int j = e[i], c = w[i];
160+
if (c + last == 0 || last == 0) {
161+
ans[j] = ans[j] == -1 ? step + 1 : Math.min(ans[j], step + 1);
162+
d.addLast(new int[]{j, c, step + 1});
163+
vis[i] = true;
164+
}
165+
}
166+
}
167+
return ans;
168+
}
169+
}
170+
```
171+
* 时间复杂度:将点数记为 `n` ,边数记为 `m`。建图复杂度为 $O(m)$;构造答案复杂度为 $O(n + m)$。整体复杂度为 $O(n + m)$
172+
* 空间复杂度:$O(n + m)$
173+
174+
---
175+
176+
### 最后
177+
178+
这是我们「刷穿 LeetCode」系列文章的第 `No.1224` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
179+
180+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
181+
182+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
183+
184+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
185+

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /