from collections import deque
class Solution:
def shortestPathAllKeys(self, grid: List[str]) -> int:
m, n = len(grid), len(grid[0])
key_count = 0
for i in range(m):
for j in range(n):
if grid[i][j] == '@':
start = (i, j)
if 'a' <= grid[i][j] <= 'f':
key_count += 1
queue = deque()
visited = set()
queue.append((start[0], start[1], 0, 0)) # row, col, keys_bitmask, steps
visited.add((start[0], start[1], 0))
target = (1 << key_count) - 1
while queue:
x, y, keys, steps = queue.popleft()
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
nx, ny = x + dx, y + dy
if not (0 <= nx < m and 0 <= ny < n):
continue
cell = grid[nx][ny]
if cell == '#':
continue
new_keys = keys
if 'a' <= cell <= 'f':
new_keys = keys | (1 << (ord(cell) - ord('a')))
if new_keys == target:
return steps + 1
if 'A' <= cell <= 'F':
if not (keys & (1 << (ord(cell.lower()) - ord('a')))):
continue
if (nx, ny, new_keys) not in visited:
visited.add((nx, ny, new_keys))
queue.append((nx, ny, new_keys, steps + 1))
return -1
#include <vector>
#include <queue>
#include <unordered_set>
using namespace std;
class Solution {
public:
int shortestPathAllKeys(vector<string>& grid) {
int m = grid.size(), n = grid[0].size();
int key_count = 0, sx, sy;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == '@') { sx = i; sy = j; }
if (grid[i][j] >= 'a' && grid[i][j] <= 'f') key_count++;
}
}
int target = (1 << key_count) - 1;
queue<tuple<int,int,int,int>> q;
set<tuple<int,int,int>> visited;
q.emplace(sx, sy, 0, 0); // x, y, keys, steps
visited.emplace(sx, sy, 0);
vector<pair<int,int>> dirs = {{1,0},{-1,0},{0,1},{0,-1}};
while (!q.empty()) {
auto [x, y, keys, steps] = q.front(); q.pop();
for (auto& d : dirs) {
int nx = x + d.first, ny = y + d.second;
if (nx < 0 || ny < 0 || nx >= m || ny >= n) continue;
char c = grid[nx][ny];
if (c == '#') continue;
int new_keys = keys;
if (c >= 'a' && c <= 'f') {
new_keys |= (1 << (c - 'a'));
if (new_keys == target) return steps + 1;
}
if (c >= 'A' && c <= 'F') {
if (!(keys & (1 << (c - 'A')))) continue;
}
if (!visited.count({nx, ny, new_keys})) {
visited.emplace(nx, ny, new_keys);
q.emplace(nx, ny, new_keys, steps + 1);
}
}
}
return -1;
}
};
import java.util.*;
class Solution {
public int shortestPathAllKeys(String[] grid) {
int m = grid.length, n = grid[0].length();
int keyCount = 0, sx = 0, sy = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
char c = grid[i].charAt(j);
if (c == '@') { sx = i; sy = j; }
if ('a' <= c && c <= 'f') keyCount++;
}
}
int target = (1 << keyCount) - 1;
Queue<int[]> q = new LinkedList<>();
Set<String> visited = new HashSet<>();
q.offer(new int[]{sx, sy, 0, 0}); // x, y, keys, steps
visited.add(sx + "," + sy + ",0");
int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
while (!q.isEmpty()) {
int[] curr = q.poll();
int x = curr[0], y = curr[1], keys = curr[2], steps = curr[3];
for (int[] d : dirs) {
int nx = x + d[0], ny = y + d[1];
if (nx < 0 || ny < 0 || nx >= m || ny >= n) continue;
char cell = grid[nx].charAt(ny);
if (cell == '#') continue;
int nkeys = keys;
if ('a' <= cell && cell <= 'f') {
nkeys = keys | (1 << (cell - 'a'));
if (nkeys == target) return steps + 1;
}
if ('A' <= cell && cell <= 'F') {
if ((keys & (1 << (cell - 'A'))) == 0) continue;
}
String state = nx + "," + ny + "," + nkeys;
if (!visited.contains(state)) {
visited.add(state);
q.offer(new int[]{nx, ny, nkeys, steps + 1});
}
}
}
return -1;
}
}
/**
* @param {string[]} grid
* @return {number}
*/
var shortestPathAllKeys = function(grid) {
const m = grid.length, n = grid[0].length;
let keyCount = 0, sx, sy;
for (let i = 0; i < m; ++i) {
for (let j = 0; j < n; ++j) {
if (grid[i][j] === '@') { sx = i; sy = j; }
if (grid[i][j] >= 'a' && grid[i][j] <= 'f') keyCount++;
}
}
const target = (1 << keyCount) - 1;
const queue = [[sx, sy, 0, 0]]; // x, y, keys, steps
const visited = new Set();
visited.add(`${sx},${sy},0`);
const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
while (queue.length) {
const [x, y, keys, steps] = queue.shift();
for (const [dx, dy] of dirs) {
const nx = x + dx, ny = y + dy;
if (nx < 0 || ny < 0 || nx >= m || ny >= n) continue;
const cell = grid[nx][ny];
if (cell === '#') continue;
let nkeys = keys;
if (cell >= 'a' && cell <= 'f') {
nkeys = keys | (1 << (cell.charCodeAt(0) - 'a'.charCodeAt(0)));
if (nkeys === target) return steps + 1;
}
if (cell >= 'A' && cell <= 'F') {
if (!(keys & (1 << (cell.charCodeAt(0) - 'A'.charCodeAt(0))))) continue;
}
const state = `${nx},${ny},${nkeys}`;
if (!visited.has(state)) {
visited.add(state);
queue.push([nx, ny, nkeys, steps + 1]);
}
}
}
return -1;
};
You are given a 2D grid representing a maze. The grid contains:
@
: Your starting position (only one in the grid).
: An empty cell you can walk through#
: A wall you cannot passa
to f
: Keys you can collect (up to 6 different keys)A
to F
: Locks that can only be opened with the corresponding key-1
.
When first approaching this problem, it might seem similar to a shortest path problem in a maze. However, the twist is that the state of the player depends not just on their position, but also on which keys they have collected. A brute-force approach would try all possible paths, but this quickly becomes infeasible as the number of keys increases, due to the exponential number of possible states.
To optimize, we realize that the problem can be modeled as a stateful search, where each state is a combination of the player's current position and the set of keys collected so far. We want to find the shortest sequence of moves that results in collecting all keys. Since we want the shortest path, Breadth-First Search (BFS) is a natural fit, but we must extend the state to include the keys collected.
To avoid revisiting the same state, we need to track not just positions we've visited, but the specific set of keys we had at that position. This prevents cycles and redundant work.
Here is a step-by-step breakdown of the optimized approach:
a
to f
.x
, y
)x
, y
, keys_bitmask
).#
), skip it.-1
.Let's consider the following grid:
["@.a..", "###.#", "b.A.B"]
(0,0)
(@)a
at (0,2) and b
at (2,0)A
at (2,2) and B
at (2,4)(0,0)
, move right to (0,1)
, then right to (0,2)
to collect key a
(bitmask now 01).(1,2)
(it's a wall, so not possible). Instead, backtrack and go down to (1,0)
(blocked by wall), so try another path.(0,2)
, back to (0,1)
, then to (1,1)
(blocked), so try moving to (0,3)
, then (0,4)
, down to (1,4)
(blocked), so that's not possible.(0,2)
, try going back to (0,1)
, then to (1,1)
(blocked), so try from start to (2,0)
to collect b
(bitmask now 11).Brute-force approach: Would be exponential, as it would try all possible permutations of paths and key pickups, leading to O((m*n)*(2^K)*K!), where K is the number of keys. This is not feasible for K up to 6.
Optimized BFS approach:
The problem is a classic example of a shortest path search with extra state (the set of collected keys). By modeling the state as (position, keys_bitmask) and using BFS, we ensure we find the shortest path efficiently. The use of a bitmask for keys is both elegant and efficient, and tracking visited states in this way prevents unnecessary recomputation. This makes the solution both optimal and easy to reason about, leveraging classic BFS with a twist for stateful traversal.