LeetCodee

864. Shortest Path to Get All Keys

Jump to Solution: Python Java C++ JavaScript C#

Problem Description

You are given an m x n grid grid where:

  • '.' is an empty cell.
  • '#' is a wall.
  • '@' is the starting point.
  • Lowercase letters represent keys.
  • Uppercase letters represent locks.

You start at the starting point and one move consists of walking one space in one of the four cardinal directions. You cannot walk outside the grid, or walk into a wall.

If you walk over a key, you can pick it up and you cannot walk over a lock unless you have its corresponding key. Return the lowest number of moves to acquire all keys. If it is impossible, return -1.

Examples:

Example 1:

Input: grid = ["@.a.#","###.#","b.A.B"]
Output: 8
Explanation: Note that the goal is to obtain all the keys not to open all the locks.

Example 2:

Input: grid = ["@..aA","..B#.","....b"]
Output: 6

Example 3:

Input: grid = ["@Aa"]
Output: -1

Constraints:

  • m == grid.length
  • n == grid[i].length
  • 1 ≤ m, n ≤ 30
  • grid[i][j] is either an empty cell, a wall, the starting point, a key, or a lock.
  • The number of keys is in the range [1, 6].
  • Each key has a different lowercase letter and its corresponding lock has the same letter in uppercase.
  • The starting point '@' appears exactly once in the grid.

Python Solution

class Solution:
    def shortestPathAllKeys(self, grid: List[str]) -> int:
        m, n = len(grid), len(grid[0])
        # Find starting position and count keys
        start_x = start_y = key_count = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] == '@':
                    start_x, start_y = i, j
                elif grid[i][j].islower():
                    key_count += 1
        
        # State: (x, y, keys)
        # keys is a bitmask where the i-th bit represents whether we have the i-th key
        queue = deque([(start_x, start_y, 0, 0)])  # (x, y, keys, steps)
        seen = {(start_x, start_y, 0)}
        
        while queue:
            x, y, keys, steps = queue.popleft()
            
            # Try all four directions
            for nx, ny in [(x+1,y), (x-1,y), (x,y+1), (x,y-1)]:
                if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] != '#':
                    cell = grid[nx][ny]
                    
                    # If it's a lock and we don't have the key, skip
                    if cell.isupper() and not (keys & (1 << (ord(cell.lower()) - ord('a')))):
                        continue
                    
                    new_keys = keys
                    # If it's a key, add it to our keyring
                    if cell.islower():
                        new_keys |= 1 << (ord(cell) - ord('a'))
                        if bin(new_keys).count('1') == key_count:
                            return steps + 1
                    
                    if (nx, ny, new_keys) not in seen:
                        seen.add((nx, ny, new_keys))
                        queue.append((nx, ny, new_keys, steps + 1))
        
        return -1

Implementation Notes:

  • Uses BFS with state encoding (position + keys collected)
  • Time complexity: O(m*n*2^k) where k is number of keys
  • Space complexity: O(m*n*2^k)

Java Solution

class Solution {
    public int shortestPathAllKeys(String[] grid) {
        int m = grid.length, n = grid[0].length();
        int startX = 0, startY = 0, keyCount = 0;
        
        // Find starting position and count keys
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                char cell = grid[i].charAt(j);
                if (cell == '@') {
                    startX = i;
                    startY = j;
                } else if (Character.isLowerCase(cell)) {
                    keyCount++;
                }
            }
        }
        
        // State: (x, y, keys)
        Queue queue = new LinkedList<>();
        queue.offer(new int[]{startX, startY, 0, 0}); // (x, y, keys, steps)
        Set seen = new HashSet<>();
        seen.add(startX + "," + startY + ",0");
        
        int[][] dirs = {{1,0}, {-1,0}, {0,1}, {0,-1}};
        
        while (!queue.isEmpty()) {
            int[] curr = queue.poll();
            int x = curr[0], y = curr[1], keys = curr[2], steps = curr[3];
            
            for (int[] dir : dirs) {
                int nx = x + dir[0], ny = y + dir[1];
                
                if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx].charAt(ny) != '#') {
                    char cell = grid[nx].charAt(ny);
                    
                    // If it's a lock and we don't have the key, skip
                    if (Character.isUpperCase(cell) && 
                        (keys & (1 << (Character.toLowerCase(cell) - 'a'))) == 0) {
                        continue;
                    }
                    
                    int newKeys = keys;
                    // If it's a key, add it to our keyring
                    if (Character.isLowerCase(cell)) {
                        newKeys |= 1 << (cell - 'a');
                        if (Integer.bitCount(newKeys) == keyCount) {
                            return steps + 1;
                        }
                    }
                    
                    String state = nx + "," + ny + "," + newKeys;
                    if (!seen.contains(state)) {
                        seen.add(state);
                        queue.offer(new int[]{nx, ny, newKeys, steps + 1});
                    }
                }
            }
        }
        
        return -1;
    }
}

C++ Solution

class Solution {
public:
    int shortestPathAllKeys(vector& grid) {
        int m = grid.size(), n = grid[0].size();
        int startX = 0, startY = 0, keyCount = 0;
        
        // Find starting position and count keys
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '@') {
                    startX = i;
                    startY = j;
                } else if (islower(grid[i][j])) {
                    keyCount++;
                }
            }
        }
        
        // State: (x, y, keys)
        queue> q;
        q.push({startX, startY, 0, 0}); // (x, y, keys, steps)
        set seen;
        seen.insert(to_string(startX) + "," + to_string(startY) + ",0");
        
        vector> dirs = {{1,0}, {-1,0}, {0,1}, {0,-1}};
        
        while (!q.empty()) {
            auto curr = q.front();
            q.pop();
            int x = curr[0], y = curr[1], keys = curr[2], steps = curr[3];
            
            for (auto& [dx, dy] : dirs) {
                int nx = x + dx, ny = y + dy;
                
                if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] != '#') {
                    char cell = grid[nx][ny];
                    
                    // If it's a lock and we don't have the key, skip
                    if (isupper(cell) && !(keys & (1 << (tolower(cell) - 'a')))) {
                        continue;
                    }
                    
                    int newKeys = keys;
                    // If it's a key, add it to our keyring
                    if (islower(cell)) {
                        newKeys |= 1 << (cell - 'a');
                        if (__builtin_popcount(newKeys) == keyCount) {
                            return steps + 1;
                        }
                    }
                    
                    string state = to_string(nx) + "," + to_string(ny) + "," + 
                                 to_string(newKeys);
                    if (seen.find(state) == seen.end()) {
                        seen.insert(state);
                        q.push({nx, ny, newKeys, steps + 1});
                    }
                }
            }
        }
        
        return -1;
    }
};

JavaScript Solution

/**
 * @param {string[]} grid
 * @return {number}
 */
var shortestPathAllKeys = function(grid) {
    const m = grid.length, n = grid[0].length;
    let startX = 0, startY = 0, keyCount = 0;
    
    // Find starting position and count keys
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (grid[i][j] === '@') {
                startX = i;
                startY = j;
            } else if (/[a-z]/.test(grid[i][j])) {
                keyCount++;
            }
        }
    }
    
    // State: (x, y, keys)
    const queue = [[startX, startY, 0, 0]]; // (x, y, keys, steps)
    const seen = new Set([`${startX},${startY},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 && nx < m && ny >= 0 && ny < n && grid[nx][ny] !== '#') {
                const cell = grid[nx][ny];
                
                // If it's a lock and we don't have the key, skip
                if (/[A-Z]/.test(cell) && 
                    !(keys & (1 << (cell.toLowerCase().charCodeAt(0) - 97)))) {
                    continue;
                }
                
                let newKeys = keys;
                // If it's a key, add it to our keyring
                if (/[a-z]/.test(cell)) {
                    newKeys |= 1 << (cell.charCodeAt(0) - 97);
                    if (newKeys.toString(2).split('1').length - 1 === keyCount) {
                        return steps + 1;
                    }
                }
                
                const state = `${nx},${ny},${newKeys}`;
                if (!seen.has(state)) {
                    seen.add(state);
                    queue.push([nx, ny, newKeys, steps + 1]);
                }
            }
        }
    }
    
    return -1;
};

C# Solution

public class Solution {
    public int ShortestPathAllKeys(string[] grid) {
        int m = grid.Length, n = grid[0].Length;
        int startX = 0, startY = 0, keyCount = 0;
        
        // Find starting position and count keys
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '@') {
                    startX = i;
                    startY = j;
                } else if (char.IsLower(grid[i][j])) {
                    keyCount++;
                }
            }
        }
        
        // State: (x, y, keys)
        var queue = new Queue<(int x, int y, int keys, int steps)>();
        queue.Enqueue((startX, startY, 0, 0));
        var seen = new HashSet();
        seen.Add($"{startX},{startY},0");
        
        var dirs = new (int dx, int dy)[] { (1,0), (-1,0), (0,1), (0,-1) };
        
        while (queue.Count > 0) {
            var (x, y, keys, steps) = queue.Dequeue();
            
            foreach (var (dx, dy) in dirs) {
                int nx = x + dx, ny = y + dy;
                
                if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] != '#') {
                    char cell = grid[nx][ny];
                    
                    // If it's a lock and we don't have the key, skip
                    if (char.IsUpper(cell) && 
                        (keys & (1 << (char.ToLower(cell) - 'a'))) == 0) {
                        continue;
                    }
                    
                    int newKeys = keys;
                    // If it's a key, add it to our keyring
                    if (char.IsLower(cell)) {
                        newKeys |= 1 << (cell - 'a');
                        if (System.Numerics.BitOperations.PopCount((uint)newKeys) == keyCount) {
                            return steps + 1;
                        }
                    }
                    
                    string state = $"{nx},{ny},{newKeys}";
                    if (!seen.Contains(state)) {
                        seen.Add(state);
                        queue.Enqueue((nx, ny, newKeys, steps + 1));
                    }
                }
            }
        }
        
        return -1;
    }
}

Implementation Notes:

  • Uses BFS with state encoding for position and collected keys
  • Uses bit manipulation for efficient key tracking
  • Time complexity: O(m*n*2^k) where k is number of keys
  • Space complexity: O(m*n*2^k)