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 d5c7d99

Browse files
✨feat: add 864
1 parent d4b3d60 commit d5c7d99

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

‎Index/图论 BFS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
| [815. 公交路线](https://leetcode-cn.com/problems/bus-routes/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/bus-routes/solution/gong-shui-san-xie-yi-ti-shuang-jie-po-su-1roh/) | 困难 | 🤩🤩🤩🤩 |
1212
| [847. 访问所有节点的最短路径](https://leetcode-cn.com/problems/shortest-path-visiting-all-nodes/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/shortest-path-visiting-all-nodes/solution/gong-shui-san-xie-yi-ti-shuang-jie-bfs-z-6p2k/) | 困难 | 🤩🤩🤩🤩🤩 |
1313
| [863. 二叉树中所有距离为 K 的结点](https://leetcode-cn.com/problems/all-nodes-distance-k-in-binary-tree/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/all-nodes-distance-k-in-binary-tree/solution/gong-shui-san-xie-yi-ti-shuang-jie-jian-x6hak/) | 中等 | 🤩🤩🤩🤩 |
14+
| [864. 获取所有钥匙的最短路径](https://leetcode.cn/problems/shortest-path-to-get-all-keys/) | [LeetCode 题解链接](https://leetcode.cn/problems/shortest-path-to-get-all-keys/solution/by-ac_oier-5gxc/) | 困难 | 🤩🤩🤩🤩🤩 |
1415
| [909. 蛇梯棋](https://leetcode-cn.com/problems/snakes-and-ladders/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/snakes-and-ladders/solution/gong-shui-san-xie-bfs-mo-ni-by-ac_oier-woh6/) | 中等 | 🤩🤩🤩🤩 |
1516
| [1020. 飞地的数量](https://leetcode-cn.com/problems/number-of-enclaves/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/number-of-enclaves/solution/gong-shui-san-xie-bing-cha-ji-dfs-yun-yo-oyh1/) | 中等 | 🤩🤩🤩 |
1617
| [1034. 边界着色](https://leetcode-cn.com/problems/coloring-a-border/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/coloring-a-border/solution/gong-shui-san-xie-tu-lun-sou-suo-zhuan-t-snvw/) | 中等 | 🤩🤩🤩🤩 |
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
### 题目描述
2+
3+
这是 LeetCode 上的 **[864. 获取所有钥匙的最短路径](https://leetcode.cn/problems/shortest-path-to-get-all-keys/solution/by-ac_oier-5gxc/)** ,难度为 **困难**
4+
5+
Tag : 「BFS」、「状态压缩」
6+
7+
8+
9+
给定一个二维网格 grid ,其中:
10+
11+
* `'.'` 代表一个空房间
12+
* `'#'` 代表一堵墙
13+
* `'@'` 是起点
14+
* 小写字母代表钥匙
15+
* 大写字母代表锁
16+
17+
我们从起点开始出发,一次移动是指向四个基本方向之一行走一个单位空间。我们不能在网格外面行走,也无法穿过一堵墙。如果途经一个钥匙,我们就把它捡起来。除非我们手里有对应的钥匙,否则无法通过锁。
18+
19+
假设 `k` 为 钥匙/锁 的个数,且满足 1ドル <= k <= 6,ドル字母表中的前 `k` 个字母在网格中都有自己对应的一个小写和一个大写字母。换言之,每个锁有唯一对应的钥匙,每个钥匙也有唯一对应的锁。另外,代表钥匙和锁的字母互为大小写并按字母顺序排列。
20+
21+
返回获取所有钥匙所需要的移动的最少次数。如果无法获取所有钥匙,返回 `-1`
22+
23+
示例 1:
24+
![](https://assets.leetcode.com/uploads/2021/07/23/lc-keys2.jpg)
25+
```
26+
输入:grid = ["@.a.#","###.#","b.A.B"]
27+
28+
输出:8
29+
30+
解释:目标是获得所有钥匙,而不是打开所有锁。
31+
```
32+
示例 2:
33+
![](https://assets.leetcode.com/uploads/2021/07/23/lc-key2.jpg)
34+
```
35+
输入:grid = ["@..aA","..B#.","....b"]
36+
37+
输出:6
38+
```
39+
示例 3:
40+
![](https://assets.leetcode.com/uploads/2021/07/23/lc-keys3.jpg)
41+
```
42+
输入: grid = ["@Aa"]
43+
44+
输出: -1
45+
```
46+
47+
提示:
48+
* $m == grid.length$
49+
* $n == grid[i].length$
50+
* 1ドル <= m, n <= 30$
51+
* `grid[i][j]` 只含有 `'.'`,`'#'`, `'@'`, `'a'-'f'` 以及 `'A'-'F'`
52+
* 钥匙的数目范围是 $[1, 6]$
53+
* 每个钥匙都对应一个 不同 的字母
54+
* 每个钥匙正好打开一个对应的锁
55+
56+
---
57+
58+
### BFS + 状态压缩
59+
60+
**一道常规的 `BFS` 运用题,只不过需要在 `BFS` 过程中记录收集到的钥匙状态。**
61+
62+
利用「钥匙数量不超过 6ドル,ドル并按字母顺序排列」,我们可以使用一个 `int` 类型二进制数 `state` 来代指当前收集到钥匙情况:
63+
64+
*`state` 的二进制中的第 $k$ 位为 `1`,代表当前种类编号为 $k$ 的钥匙 **已被收集**,后续移动若遇到对应的锁则 **能通过**
65+
*`state` 的二进制中的第 $k$ 位为 `0`,代表当前种类编号为 $k$ 的钥匙 **未被收集**,后续移动若遇到对应的锁则 **无法通过**
66+
67+
其中「钥匙种类编号」则按照小写字母先后顺序,从 0ドル$ 开始进行划分对应:即字符为 `a` 的钥匙编号为 `0`,字符为 `b` 的钥匙编号为 `1`,字符为 `c` 的钥匙编号为 `2` ...
68+
69+
当使用了这样的「状态压缩」技巧后,我们可以很方便通过「位运算」进行 **钥匙检测****更新钥匙收集状态**:
70+
71+
* 钥匙检测:`(state >> k) & 1`,若返回 `1` 说明第 $k$ 位为 `1`,当前持有种类编号为 `k` 的钥匙
72+
* 更新钥匙收集状态:`state |= 1 << k`,将 `state` 的第 $k$ 位设置为 `1`,代表当前新收集到种类编号为 `k` 的钥匙
73+
74+
搞明白如何记录当前收集到的钥匙状态后,剩下的则是常规 `BFS` 过程:
75+
76+
1. 起始遍历一次棋盘,找到起点位置,并将其进行入队,队列维护的是 $(x, y, state)$ 三元组状态(其中 $(x, y)$ 代表当前所在的棋盘位置,$state$ 代表当前的钥匙收集情况)
77+
同时统计整个棋盘所包含的钥匙数量 `cnt`,并使用 数组/哈希表 记录到达每个状态所需要消耗的最小步数 `step`
78+
79+
2. 进行四联通方向的 `BFS`,转移过程中需要注意「遇到锁时,必须有对应钥匙才能通过」&「遇到钥匙时,需要更新对应的 `state` 再进行入队」
80+
81+
3.`BFS` 过程中遇到 `state = (1 << cnt) - 1` 时,代表所有钥匙均被收集完成,可结束搜索
82+
83+
Java 代码:
84+
```Java
85+
class Solution {
86+
static int N = 35, K = 10, INF = 0x3f3f3f3f;
87+
static int[][][] dist = new int[N][N][1 << K];
88+
static int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
89+
public int shortestPathAllKeys(String[] g) {
90+
int n = g.length, m = g[0].length(), cnt = 0;
91+
Deque<int[]> d = new ArrayDeque<>();
92+
for (int i = 0; i < n; i++) {
93+
for (int j = 0; j < m; j++) {
94+
Arrays.fill(dist[i][j], INF);
95+
char c = g[i].charAt(j);
96+
if (c == '@') {
97+
d.addLast(new int[]{i, j, 0});
98+
dist[i][j][0] = 0;
99+
} else if (c >= 'a' && c <= 'z') cnt++;
100+
}
101+
}
102+
while (!d.isEmpty()) {
103+
int[] info = d.pollFirst();
104+
int x = info[0], y = info[1], cur = info[2], step = dist[x][y][cur];
105+
for (int[] di : dirs) {
106+
int nx = x + di[0], ny = y + di[1];
107+
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
108+
char c = g[nx].charAt(ny);
109+
if (c == '#') continue;
110+
if ((c >= 'A' && c <= 'Z') && (cur >> (c - 'A') & 1) == 0) continue;
111+
int ncur = cur;
112+
if (c >= 'a' && c <= 'z') ncur |= 1 << (c - 'a');
113+
if (ncur == (1 << cnt) - 1) return step + 1;
114+
if (step + 1 >= dist[nx][ny][ncur]) continue;
115+
dist[nx][ny][ncur] = step + 1;
116+
d.addLast(new int[]{nx, ny, ncur});
117+
}
118+
}
119+
return -1;
120+
}
121+
}
122+
```
123+
TypeScript 代码:
124+
```TypeScript
125+
function shortestPathAllKeys(g: string[]): number {
126+
const dirs = [[1,0],[-1,0],[0,1],[0,-1]]
127+
let n = g.length, m = g[0].length, cnt = 0
128+
const dist = new Array<Array<Array<number>>>()
129+
for (let i = 0; i < n; i++) {
130+
dist[i] = new Array<Array<number>>(m)
131+
for (let j = 0; j < m; j++) {
132+
dist[i][j] = new Array<number>(1 << 10).fill(0x3f3f3f3f)
133+
}
134+
}
135+
const d = []
136+
for (let i = 0; i < n; i++) {
137+
for (let j = 0; j < m; j++) {
138+
if (g[i][j] == '@') {
139+
d.push([i, j, 0]); dist[i][j][0] = 0
140+
} else if (g[i][j] >= 'a' && g[i][j] <= 'z') cnt++
141+
}
142+
}
143+
while (d.length > 0) {
144+
const info = d.shift()
145+
const x = info[0], y = info[1], cur = info[2], step = dist[x][y][cur]
146+
for (const di of dirs) {
147+
const nx = x + di[0], ny = y + di[1]
148+
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue
149+
const c = g[nx][ny]
150+
if (c == '#') continue
151+
if ('A' <= c && c <= 'Z' && ((cur >> (c.charCodeAt(0) - 'A'.charCodeAt(0)) & 1) == 0)) continue
152+
let ncur = cur
153+
if ('a' <= c && c <= 'z') ncur |= 1 << (c.charCodeAt(0) - 'a'.charCodeAt(0))
154+
if (ncur == (1 << cnt) - 1) return step + 1
155+
if (step + 1 >= dist[nx][ny][ncur]) continue
156+
d.push([nx, ny, ncur])
157+
dist[nx][ny][ncur] = step + 1
158+
}
159+
}
160+
return -1
161+
}
162+
```
163+
Python3 代码:
164+
```Python
165+
class Solution:
166+
def shortestPathAllKeys(self, g: List[str]) -> int:
167+
dirs = [[0,1], [0,-1], [1,0], [-1,0]]
168+
n, m, cnt = len(g), len(g[0]), 0
169+
dist = defaultdict(lambda : 0x3f3f3f3f)
170+
for i in range(n):
171+
for j in range(m):
172+
c = g[i][j]
173+
if c == '@':
174+
d = deque([(i, j, 0)])
175+
dist[(i, j, 0)] = 0
176+
elif 'a' <= c <= 'z':
177+
cnt += 1
178+
while d:
179+
x, y, cur = d.popleft()
180+
step = dist[(x, y, cur)]
181+
for di in dirs:
182+
nx, ny = x + di[0], y + di[1]
183+
if nx < 0 or nx >= n or ny < 0 or ny >= m:
184+
continue
185+
c = g[nx][ny]
186+
if c == '#':
187+
continue
188+
if 'A' <= c <= 'Z' and (cur >> (ord(c) - ord('A')) & 1) == 0:
189+
continue
190+
ncur = cur
191+
if 'a' <= c <= 'z':
192+
ncur |= (1 << (ord(c) - ord('a')))
193+
if ncur == (1 << cnt) - 1:
194+
return step + 1
195+
if step + 1 >= dist[(nx, ny, ncur)]:
196+
continue
197+
dist[(nx, ny, ncur)] = step + 1
198+
d.append((nx, ny, ncur))
199+
return -1
200+
```
201+
* 时间复杂度:$O(n \times m \times 2^k)$
202+
* 空间复杂度:$O(n \times m \times 2^k)$
203+
204+
---
205+
206+
### 最后
207+
208+
这是我们「刷穿 LeetCode」系列文章的第 `No.864` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。
209+
210+
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。
211+
212+
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode
213+
214+
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。
215+

0 commit comments

Comments
(0)

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