69
69
70
70
** 方法一:状态压缩 + BFS**
71
71
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
+
72
100
<!-- tabs:start -->
73
101
74
102
### ** Python3**
79
107
class Solution :
80
108
def shortestPathAllKeys (self , grid : List[str ]) -> int :
81
109
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 )}
90
117
ans = 0
91
- mask = (1 << cnt) - 1
92
- vis = {(* start, 0 )}
93
118
while q:
94
119
for _ in range (len (q)):
95
120
i, j, state = q.popleft()
96
- if state == mask:
121
+ # 找到所有钥匙,返回当前步数
122
+ if state == (1 << k) - 1 :
97
123
return ans
98
- for k in range (4 ):
124
+
125
+ # 往四个方向搜索
126
+ for a, b in pairwise(dirs):
127
+ x, y = i + a, j + b
99
128
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 :
106
134
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
+ # 此状态未访问过,入队
109
140
if (x, y, nxt) not in vis:
110
- q.append((x, y, nxt))
111
141
vis.add((x, y, nxt))
142
+ q.append((x, y, nxt))
143
+ # 步数加一
112
144
ans += 1
113
145
return - 1
114
146
```
@@ -119,54 +151,63 @@ class Solution:
119
151
120
152
``` java
121
153
class Solution {
154
+ private int [] dirs = {- 1 , 0 , 1 , 0 , - 1 };
155
+
122
156
public int shortestPathAllKeys (String [] grid ) {
123
157
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 ;
126
160
for (int i = 0 ; i < m; ++ i) {
127
161
for (int j = 0 ; j < n; ++ j) {
128
162
char c = grid[i]. charAt(j);
129
163
if (Character . isLowerCase(c)) {
130
- ++ cnt;
164
+ // 累加钥匙数量
165
+ ++ k;
131
166
} else if (c == ' @' ) {
132
- sx = i;
133
- sy = j;
167
+ // 起点
168
+ si = i;
169
+ sj = j;
134
170
}
135
171
}
136
172
}
137
173
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 ;
140
177
int ans = 0 ;
141
- int mask = (1 << cnt) - 1 ;
142
- boolean [][][] vis = new boolean [m][n][1 << cnt];
143
- vis[sx][sy][0 ] = true ;
144
178
while (! q. isEmpty()) {
145
179
for (int t = q. size(); t > 0 ; -- t) {
146
- int [] p = q. poll();
180
+ var p = q. poll();
147
181
int i = p[0 ], j = p[1 ], state = p[2 ];
148
- if (state == mask) {
182
+ // 找到所有钥匙,返回当前步数
183
+ if (state == (1 << k) - 1 ) {
149
184
return ans;
150
185
}
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
+ // 在边界范围内
154
190
if (x >= 0 && x < m && y >= 0 && y < n) {
155
191
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 )) {
158
194
continue ;
159
195
}
196
+ int nxt = state;
197
+ // 是钥匙
160
198
if (Character . isLowerCase(c)) {
199
+ // 更新状态
161
200
nxt |= 1 << (c - ' a' );
162
201
}
202
+ // 此状态未访问过,入队
163
203
if (! vis[x][y][nxt]) {
164
204
vis[x][y][nxt] = true ;
165
205
q. offer(new int [] {x, y, nxt});
166
206
}
167
207
}
168
208
}
169
209
}
210
+ // 步数加一
170
211
++ ans;
171
212
}
172
213
return - 1 ;
@@ -179,48 +220,51 @@ class Solution {
179
220
``` cpp
180
221
class Solution {
181
222
public:
223
+ const static inline vector<int > dirs = {-1, 0, 1, 0, -1};
224
+
182
225
int shortestPathAllKeys(vector<string>& grid) {
183
226
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;
186
229
for (int i = 0; i < m; ++i) {
187
230
for (int j = 0; j < n; ++j) {
188
231
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;
195
236
}
196
237
}
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;
202
241
int ans = 0;
203
- vector<int > dirs = {-1, 0, 1, 0, -1};
204
242
while (!q.empty()) {
205
243
for (int t = q.size(); t; --t) {
206
- auto p = q.front();
244
+ auto [ i, j, state ] = q.front();
207
245
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
+ // 在边界范围内
213
252
if (x >= 0 && x < m && y >= 0 && y < n) {
214
253
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
+ // 是钥匙,更新状态
216
258
if (islower(c)) nxt |= 1 << (c - 'a');
259
+ // 此状态未访问过,入队
217
260
if (!vis[ x] [ y ] [ nxt] ) {
218
261
vis[ x] [ y ] [ nxt] = true;
219
262
q.push({x, y, nxt});
220
263
}
221
264
}
222
265
}
223
266
}
267
+ // 步数加一
224
268
++ans;
225
269
}
226
270
return -1;
@@ -233,58 +277,56 @@ public:
233
277
```go
234
278
func shortestPathAllKeys(grid []string) int {
235
279
m, n := len(grid), len(grid[0])
236
- cnt := 0
237
- sx, sy := 0, 0
280
+ var k, si, sj int
238
281
for i, row := range grid {
239
282
for j, c := range row {
240
- if 'a' <= c && c <= 'z' {
241
- cnt++
283
+ if c >= 'a' && c <= 'z' {
284
+ // 累加钥匙数量
285
+ k++
242
286
} else if c == '@' {
243
- sx, sy = i, j
287
+ // 起点
288
+ si, sj = i, j
244
289
}
245
290
}
246
291
}
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}
256
295
dirs := []int{-1, 0, 1, 0, -1}
257
296
ans := 0
258
- mask := (1 << cnt) - 1
259
297
for len(q) > 0 {
260
298
for t := len(q); t > 0; t-- {
261
299
p := q[0]
262
300
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 {
265
304
return ans
266
305
}
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
+ // 在边界范围内
270
310
if x >= 0 && x < m && y >= 0 && y < n {
271
311
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)) {
276
314
continue
277
315
}
278
- if 'a' <= c && c <= 'z' {
316
+ nxt := state
317
+ // 是钥匙,更新状态
318
+ if c >= 'a' && c <= 'z' {
279
319
nxt |= 1 << (c - 'a')
280
320
}
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})
284
325
}
285
326
}
286
327
}
287
328
}
329
+ // 步数加一
288
330
ans++
289
331
}
290
332
return -1
0 commit comments