Skip to content

Commit d3cc835

Browse files
committed
feat: update solutions to lc problem: No.0864
No.0864.Shortest Path to Get All Keys
1 parent d1e3eb9 commit d3cc835

File tree

9 files changed

+325
-262
lines changed

9 files changed

+325
-262
lines changed

solution/0800-0899/0864.Shortest Path to Get All Keys/README.md

+129-87
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,34 @@
6969

7070
**方法一:状态压缩 + BFS**
7171

72+
根据题意,我们需要从起点出发,往上下左右四个方向走,获取所有钥匙,最后返回获取所有钥匙所需要的移动的最少次数。若无法获取所有钥匙,返回 $-1$。
73+
74+
首先,我们遍历二维网格,找到起点位置 $(si, sj)$,并统计钥匙的个数 $k$。
75+
76+
然后,我们可以使用广度优先搜索 $BFS$ 来解决本题。由于钥匙的个数范围是 $[1, 6]$,我们可以用一个二进制数来表示钥匙的状态,其中第 $i$ 位为 $1$ 表示第 $i$ 把钥匙已经获取到了,为 $0$ 表示第 $i$ 把钥匙还没有获取到。
77+
78+
比如,以下例子中,共有 $4$ 个位为 $1$,也就表示共有 $4$ 把钥匙已经获取到了。
79+
80+
```
81+
1 0 1 1 1 0
82+
```
83+
84+
我们定义一个队列 $q$ 来存储当前位置以及当前拥有的钥匙的状态,即 $(i, j, state)$,其中 $(i, j)$ 表示当前位置,$state$ 表示当前拥有的钥匙的状态,即 $state$ 的第 $i$ 位为 $1$ 表示当前拥有第 $i$ 把钥匙,否则表示当前没有第 $i$ 把钥匙。
85+
86+
另外,定义哈希表或数组 $vis$ 记录当前位置以及当前拥有的钥匙的状态是否已经被访问过,如果访问过,则不需要再次访问。$vis[i][j][state]$ 表示当前位置为 $(i, j)$,当前拥有的钥匙的状态为 $state$ 时,是否已经被访问过。
87+
88+
我们从起点 $(si, sj)$ 出发,将其加入队列 $q$,并将 $vis[si][sj][0]$ 置为 $true$,表示起点位置以及拥有的钥匙的状态为 $0$ 时已经被访问过。
89+
90+
在广度优先搜索的过程中,我们每次从队首取出一个位置 $(i, j, state)$,并判断当前位置是否为终点,即当前位置是否拥有所有的钥匙,即 $state$ 的二进制表示中的 $1$ 的个数是否为 $k$。如果是,将当前步数作为答案返回。
91+
92+
否则,我们从当前位置出发,往上下左右四个方向走,如果可以走到下一个位置 $(x, y)$,则将 $(x, y, nxt)$ 加入队列 $q$,其中 $nxt$ 表示下一个位置的钥匙的状态。
93+
94+
这里 $(x, y)$ 首先需要满足在网格范围内,即 $0 \leq x < m$ 且 $0 \leq y < n$。其次,如果 $(x, y)$ 位置是墙壁,即 `grid[x][y] == '#'`,或者 $(x, y)$ 位置是锁,但我们没有对应的钥匙,即 `grid[x][y] >= 'A' && grid[x][y] <= 'F' && (state >> (grid[x][y] - 'A') & 1) == 0)`,则不能走到位置 $(x, y)$。否则,我们可以走到位置 $(x, y)$。
95+
96+
搜索结束,没能找到所有的钥匙,返回 $-1$。
97+
98+
时间复杂度 $O(m\times n\times 2^k)$,空间复杂度 $O(m\times n\times 2^k)$。其中 $m$ 和 $n$ 分别为网格的行数和列数,而 $k$ 为钥匙的个数。
99+
72100
<!-- tabs:start -->
73101

74102
### **Python3**
@@ -79,36 +107,40 @@
79107
class Solution:
80108
def shortestPathAllKeys(self, grid: List[str]) -> int:
81109
m, n = len(grid), len(grid[0])
82-
cnt, start = 0, None
83-
for i, row in enumerate(grid):
84-
for j, v in enumerate(row):
85-
cnt += v.islower()
86-
if v == '@':
87-
start = (i, j)
88-
q = deque([(start[0], start[1], 0)])
89-
dirs = [-1, 0, 1, 0, -1]
110+
# 找起点 (si, sj)
111+
si, sj = next((i, j) for i in range(m) for j in range(n) if grid[i][j] == '@')
112+
# 统计钥匙数量
113+
k = sum(v.islower() for row in grid for v in row)
114+
dirs = (-1, 0, 1, 0, -1)
115+
q = deque([(si, sj, 0)])
116+
vis = {(si, sj, 0)}
90117
ans = 0
91-
mask = (1 << cnt) - 1
92-
vis = {(*start, 0)}
93118
while q:
94119
for _ in range(len(q)):
95120
i, j, state = q.popleft()
96-
if state == mask:
121+
# 找到所有钥匙,返回当前步数
122+
if state == (1 << k) - 1:
97123
return ans
98-
for k in range(4):
124+
125+
# 往四个方向搜索
126+
for a, b in pairwise(dirs):
127+
x, y = i + a, j + b
99128
nxt = state
100-
x, y = i + dirs[k], j + dirs[k + 1]
101-
if 0 <= x < m and 0 <= y < n and grid[x][y] != '#':
102-
if (
103-
grid[x][y].isupper()
104-
and (nxt & (1 << (ord(grid[x][y]) - ord('A')))) == 0
105-
):
129+
# 在边界范围内
130+
if 0 <= x < m and 0 <= y < n:
131+
c = grid[x][y]
132+
# 是墙,或者是锁,但此时没有对应的钥匙,无法通过
133+
if c == '#' or c.isupper() and (state & (1 << (ord(c) - ord('A')))) == 0:
106134
continue
107-
if grid[x][y].islower():
108-
nxt |= 1 << (ord(grid[x][y]) - ord('a'))
135+
# 是钥匙
136+
if c.islower():
137+
# 更新状态
138+
nxt |= 1 << (ord(c) - ord('a'))
139+
# 此状态未访问过,入队
109140
if (x, y, nxt) not in vis:
110-
q.append((x, y, nxt))
111141
vis.add((x, y, nxt))
142+
q.append((x, y, nxt))
143+
# 步数加一
112144
ans += 1
113145
return -1
114146
```
@@ -119,54 +151,63 @@ class Solution:
119151

120152
```java
121153
class Solution {
154+
private int[] dirs = {-1, 0, 1, 0, -1};
155+
122156
public int shortestPathAllKeys(String[] grid) {
123157
int m = grid.length, n = grid[0].length();
124-
int cnt = 0;
125-
int sx = 0, sy = 0;
158+
int k = 0;
159+
int si = 0, sj = 0;
126160
for (int i = 0; i < m; ++i) {
127161
for (int j = 0; j < n; ++j) {
128162
char c = grid[i].charAt(j);
129163
if (Character.isLowerCase(c)) {
130-
++cnt;
164+
// 累加钥匙数量
165+
++k;
131166
} else if (c == '@') {
132-
sx = i;
133-
sy = j;
167+
// 起点
168+
si = i;
169+
sj = j;
134170
}
135171
}
136172
}
137173
Deque<int[]> q = new ArrayDeque<>();
138-
q.offer(new int[] {sx, sy, 0});
139-
int[] dirs = {-1, 0, 1, 0, -1};
174+
q.offer(new int[] {si, sj, 0});
175+
boolean[][][] vis = new boolean[m][n][1 << k];
176+
vis[si][sj][0] = true;
140177
int ans = 0;
141-
int mask = (1 << cnt) - 1;
142-
boolean[][][] vis = new boolean[m][n][1 << cnt];
143-
vis[sx][sy][0] = true;
144178
while (!q.isEmpty()) {
145179
for (int t = q.size(); t > 0; --t) {
146-
int[] p = q.poll();
180+
var p = q.poll();
147181
int i = p[0], j = p[1], state = p[2];
148-
if (state == mask) {
182+
// 找到所有钥匙,返回当前步数
183+
if (state == (1 << k) - 1) {
149184
return ans;
150185
}
151-
for (int k = 0; k < 4; ++k) {
152-
int nxt = state;
153-
int x = i + dirs[k], y = j + dirs[k + 1];
186+
// 往四个方向搜索
187+
for (int h = 0; h < 4; ++h) {
188+
int x = i + dirs[h], y = j + dirs[h + 1];
189+
// 在边界范围内
154190
if (x >= 0 && x < m && y >= 0 && y < n) {
155191
char c = grid[x].charAt(y);
156-
if (c == '#'
157-
|| (Character.isUpperCase(c) && (nxt & (1 << (c - 'A'))) == 0)) {
192+
// 是墙,或者是锁,但此时没有对应的钥匙,无法通过
193+
if (c == '#' || (Character.isUpperCase(c) && ((state >> (c - 'A')) & 1) == 0)) {
158194
continue;
159195
}
196+
int nxt = state;
197+
// 是钥匙
160198
if (Character.isLowerCase(c)) {
199+
// 更新状态
161200
nxt |= 1 << (c - 'a');
162201
}
202+
// 此状态未访问过,入队
163203
if (!vis[x][y][nxt]) {
164204
vis[x][y][nxt] = true;
165205
q.offer(new int[] {x, y, nxt});
166206
}
167207
}
168208
}
169209
}
210+
// 步数加一
170211
++ans;
171212
}
172213
return -1;
@@ -179,48 +220,51 @@ class Solution {
179220
```cpp
180221
class Solution {
181222
public:
223+
const static inline vector<int> dirs = {-1, 0, 1, 0, -1};
224+
182225
int shortestPathAllKeys(vector<string>& grid) {
183226
int m = grid.size(), n = grid[0].size();
184-
int cnt = 0;
185-
int sx = 0, sy = 0;
227+
int k = 0;
228+
int si = 0, sj = 0;
186229
for (int i = 0; i < m; ++i) {
187230
for (int j = 0; j < n; ++j) {
188231
char c = grid[i][j];
189-
if (islower(c))
190-
++cnt;
191-
else if (c == '@') {
192-
sx = i;
193-
sy = j;
194-
}
232+
// 累加钥匙数量
233+
if (islower(c)) ++k;
234+
// 起点
235+
else if (c == '@') si = i, sj = j;
195236
}
196237
}
197-
queue<vector<int>> q;
198-
q.push({sx, sy, 0});
199-
int mask = (1 << cnt) - 1;
200-
vector<vector<vector<bool>>> vis(m, vector<vector<bool>>(n, vector<bool>(1 << cnt)));
201-
vis[sx][sy][0] = true;
238+
queue<tuple<int, int, int>> q{{{si, sj, 0}}};
239+
vector<vector<vector<bool>>> vis(m, vector<vector<bool>>(n, vector<bool>(1 << k)));
240+
vis[si][sj][0] = true;
202241
int ans = 0;
203-
vector<int> dirs = {-1, 0, 1, 0, -1};
204242
while (!q.empty()) {
205243
for (int t = q.size(); t; --t) {
206-
auto p = q.front();
244+
auto [i, j, state] = q.front();
207245
q.pop();
208-
int i = p[0], j = p[1], state = p[2];
209-
if (state == mask) return ans;
210-
for (int k = 0; k < 4; ++k) {
211-
int nxt = state;
212-
int x = i + dirs[k], y = j + dirs[k + 1];
246+
// 找到所有钥匙,返回当前步数
247+
if (state == (1 << k) - 1) return ans;
248+
// 往四个方向搜索
249+
for (int h = 0; h < 4; ++h) {
250+
int x = i + dirs[h], y = j + dirs[h + 1];
251+
// 在边界范围内
213252
if (x >= 0 && x < m && y >= 0 && y < n) {
214253
char c = grid[x][y];
215-
if (c == '#' || (isupper(c) && (nxt & (1 << (c - 'A'))) == 0)) continue;
254+
// 是墙,或者是锁,但此时没有对应的钥匙,无法通过
255+
if (c == '#' || (isupper(c) && (state >> (c - 'A') & 1) == 0)) continue;
256+
int nxt = state;
257+
// 是钥匙,更新状态
216258
if (islower(c)) nxt |= 1 << (c - 'a');
259+
// 此状态未访问过,入队
217260
if (!vis[x][y][nxt]) {
218261
vis[x][y][nxt] = true;
219262
q.push({x, y, nxt});
220263
}
221264
}
222265
}
223266
}
267+
// 步数加一
224268
++ans;
225269
}
226270
return -1;
@@ -233,58 +277,56 @@ public:
233277
```go
234278
func shortestPathAllKeys(grid []string) int {
235279
m, n := len(grid), len(grid[0])
236-
cnt := 0
237-
sx, sy := 0, 0
280+
var k, si, sj int
238281
for i, row := range grid {
239282
for j, c := range row {
240-
if 'a' <= c && c <= 'z' {
241-
cnt++
283+
if c >= 'a' && c <= 'z' {
284+
// 累加钥匙数量
285+
k++
242286
} else if c == '@' {
243-
sx, sy = i, j
287+
// 起点
288+
si, sj = i, j
244289
}
245290
}
246291
}
247-
q := [][]int{{sx, sy, 0}}
248-
vis := make([][][]bool, m)
249-
for i := range vis {
250-
vis[i] = make([][]bool, n)
251-
for j := range vis[i] {
252-
vis[i][j] = make([]bool, 1<<cnt)
253-
}
254-
}
255-
vis[sx][sy][0] = true
292+
type tuple struct{ i, j, state int }
293+
q := []tuple{tuple{si, sj, 0}}
294+
vis := map[tuple]bool{tuple{si, sj, 0}: true}
256295
dirs := []int{-1, 0, 1, 0, -1}
257296
ans := 0
258-
mask := (1 << cnt) - 1
259297
for len(q) > 0 {
260298
for t := len(q); t > 0; t-- {
261299
p := q[0]
262300
q = q[1:]
263-
i, j, state := p[0], p[1], p[2]
264-
if state == mask {
301+
i, j, state := p.i, p.j, p.state
302+
// 找到所有钥匙,返回当前步数
303+
if state == 1<<k-1 {
265304
return ans
266305
}
267-
for k := 0; k < 4; k++ {
268-
nxt := state
269-
x, y := i+dirs[k], j+dirs[k+1]
306+
// 往四个方向搜索
307+
for h := 0; h < 4; h++ {
308+
x, y := i+dirs[h], j+dirs[h+1]
309+
// 在边界范围内
270310
if x >= 0 && x < m && y >= 0 && y < n {
271311
c := grid[x][y]
272-
if c == '#' {
273-
continue
274-
}
275-
if 'A' <= c && c <= 'Z' && (nxt&(1<<(c-'A'))) == 0 {
312+
// 是墙,或者是锁,但此时没有对应的钥匙,无法通过
313+
if c == '#' || (c >= 'A' && c <= 'Z' && (state>>(c-'A')&1 == 0)) {
276314
continue
277315
}
278-
if 'a' <= c && c <= 'z' {
316+
nxt := state
317+
// 是钥匙,更新状态
318+
if c >= 'a' && c <= 'z' {
279319
nxt |= 1 << (c - 'a')
280320
}
281-
if !vis[x][y][nxt] {
282-
vis[x][y][nxt] = true
283-
q = append(q, []int{x, y, nxt})
321+
// 此状态未访问过,入队
322+
if !vis[tuple{x, y, nxt}] {
323+
vis[tuple{x, y, nxt}] = true
324+
q = append(q, tuple{x, y, nxt})
284325
}
285326
}
286327
}
287328
}
329+
// 步数加一
288330
ans++
289331
}
290332
return -1

0 commit comments

Comments
 (0)