Shortest Path to Get All Keys — Hard Problem & Solution
You are given an m x n grid as a list of strings, one per row: '.' is an empty cell and '#' is a wall, '@' is the starting point (there is exactly one),…
- Difficulty: Hard
- Topics: Arrays, Matrix, Bit Manipulation, Breadth-First Search
- Asked at: Amazon, Google
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
You are given an m x n grid as a list of strings, one per row:
'.'is an empty cell and'#'is a wall,'@'is the starting point (there is exactly one),- lowercase letters
'a'–'f'are keys and uppercase letters'A'–'F'are locks.
Each move goes one cell up, down, left or right. You cannot leave the grid or enter a wall. Walking onto a key picks it up; you may enter a lock only if you already hold its matching key.
The grid holds k keys (1 <= k <= 6), and they are exactly the first k letters of the alphabet, each with exactly one matching lock. Return the fewest moves needed to collect all keys, or -1 if that is impossible.
Example 1
Input: grid = ["@.#b","a.A.","#..B"]
Output: 5
Explanation: Take `a` (1 move), then go (1,1) → (1,2) through lock `A` → (1,3) → (0,3) for `b`.
Example 2
Input: grid = ["@#a"]
Output: -1
Explanation: The wall cuts the start off from the only key.
Example 3
Input: grid = ["Aa@bB"]
Output: 3
Constraints
m == grid.lengthn == grid[i].length1 <= m, n <= 30grid[i][j] is an English letter, '.', '#' or '@'there is exactly one '@' in the gridthe number of keys is in the range [1, 6]each key is unique and has a matching lockthe keys and locks are the first k letters of the alphabet
How to solve Shortest Path to Get All Keys
Breadth-first search over the augmented state (cell, key mask); every move costs 1, so the first time the mask is full is the shortest route.
Approach
- Find the start and the number of keys
k; the goal mask is2^k − 1. - BFS from
(start, 0), marking states visited in anm × n × 2^ktable. - From a state, try the four neighbours: skip walls, skip a lock whose key bit is clear, and OR in the bit of a key.
- Return
distance + 1as soon as a new state has the full mask; return -1 if the BFS ends.
Why it works
Carrying the key set in the state makes the move rules depend only on the state, so the problem is an unweighted shortest path in a finite graph of at most m · n · 64 states, which BFS solves exactly. Revisiting a cell with a different key set is allowed and necessary — that is how routes that fetch a key and come back are found.
Complexity
- Time —
O(m · n · 2^k) - Space —
O(m · n · 2^k)
Pitfalls
- Marking a cell visited regardless of the key mask blocks the back-tracking routes the puzzle needs.
- A lock without its key is a wall for now, not forever.
- The goal is all keys, not any particular cell — check the mask, not the position.
Reference solution
Python
from typing import List
from collections import deque
def shortestPathAllKeys(grid: List[str]) -> int:
m, n = len(grid), len(grid[0])
keys = 0
sr = sc = 0
for r in range(m):
for c in range(n):
ch = grid[r][c]
if ch == '@':
sr, sc = r, c
elif 'a' <= ch <= 'f':
keys = max(keys, ord(ch) - 96)
full = (1 << keys) - 1
seen = [[[False] * (1 << keys) for _ in range(n)] for _ in range(m)]
seen[sr][sc][0] = True
q = deque([(sr, sc, 0, 0)])
while q:
r, c, mask, d = q.popleft()
for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
nr, nc = r + dr, c + dc
if nr < 0 or nr >= m or nc < 0 or nc >= n:
continue
ch = grid[nr][nc]
if ch == '#':
continue
if 'A' <= ch <= 'F' and not (mask >> (ord(ch) - 65)) & 1:
continue
nm = mask
if 'a' <= ch <= 'f':
nm |= 1 << (ord(ch) - 97)
if seen[nr][nc][nm]:
continue
if nm == full:
return d + 1
seen[nr][nc][nm] = True
q.append((nr, nc, nm, d + 1))
return -1JavaScript
var shortestPathAllKeys = function(grid) {
var m = grid.length, n = grid[0].length, keys = 0, start = 0;
for (var r = 0; r < m; r++) {
for (var c = 0; c < n; c++) {
var ch = grid[r].charCodeAt(c);
if (ch === 64) start = r * n + c;
else if (ch >= 97 && ch <= 102 && ch - 96 > keys) keys = ch - 96;
}
}
var full = (1 << keys) - 1;
var seen = [];
for (var i = 0; i < m * n * 64; i++) seen.push(false);
var qc = [start], qm = [0], qd = [0];
seen[start * 64] = true;
var dr = [1, -1, 0, 0], dc = [0, 0, 1, -1];
for (var h = 0; h < qc.length; h++) {
var cr = Math.floor(qc[h] / n), cc = qc[h] % n, mask = qm[h], d = qd[h];
for (var k = 0; k < 4; k++) {
var nr = cr + dr[k], nc = cc + dc[k];
if (nr < 0 || nr >= m || nc < 0 || nc >= n) continue;
var x = grid[nr].charCodeAt(nc);
if (x === 35) continue;
if (x >= 65 && x <= 70 && ((mask >> (x - 65)) & 1) === 0) continue;
var nm = mask;
if (x >= 97 && x <= 102) nm |= 1 << (x - 97);
var id = (nr * n + nc) * 64 + nm;
if (seen[id]) continue;
if (nm === full) return d + 1;
seen[id] = true;
qc.push(nr * n + nc); qm.push(nm); qd.push(d + 1);
}
}
return -1;
};Also on the editorial tab: C, C#, C++, Go, Java, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 988 arrays problems · the whole catalogue
Learn the technique: Arrays · Matrix and Grid Traversal