|
| 1 | +package graph; |
| 2 | + |
| 3 | +import java.util.LinkedList; |
| 4 | +import java.util.Queue; |
| 5 | + |
| 6 | +/** |
| 7 | + * Description: https://leetcode.com/problems/shortest-path-to-get-all-keys |
| 8 | + * Difficulty: Hard |
| 9 | + * Time complexity: O(2^k * m * n) |
| 10 | + * Space complexity: O(2^k * m * n) |
| 11 | + */ |
| 12 | +public class ShortestPathToGetAllKeys { |
| 13 | + |
| 14 | + private final int[][] directions = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; |
| 15 | + |
| 16 | + public int shortestPathAllKeys(String[] lines) { |
| 17 | + char[][] grid = toGrid(lines); |
| 18 | + |
| 19 | + int keysNumber = 0; |
| 20 | + int x = -1; |
| 21 | + int y = -1; |
| 22 | + for (int i = 0; i < grid.length; i++) { |
| 23 | + for (int j = 0; j < grid[0].length; j++) { |
| 24 | + if (isKey(grid[i][j])) { |
| 25 | + keysNumber++; |
| 26 | + } else if (isStart(grid[i][j])) { |
| 27 | + x = i; |
| 28 | + y = j; |
| 29 | + } |
| 30 | + } |
| 31 | + } |
| 32 | + |
| 33 | + return findShortestPath(grid, keysNumber, x, y); |
| 34 | + } |
| 35 | + |
| 36 | + private int findShortestPath(char[][] grid, int keysNumber, int x, int y) { |
| 37 | + // 1 << 5 - 1 = 100000 - 1 = 11111 |
| 38 | + int allKeysFoundMask = (1 << keysNumber) - 1; |
| 39 | + |
| 40 | + // we may visit the same node multiple times with a different set of keys |
| 41 | + int[][][] visited = new int[grid.length][grid[0].length][allKeysFoundMask]; |
| 42 | + visited[x][y][0] = 1; |
| 43 | + |
| 44 | + Queue<Node> planned = new LinkedList<>(); |
| 45 | + planned.offer(new Node(x, y, 0)); |
| 46 | + |
| 47 | + int distance = 0; |
| 48 | + while (!planned.isEmpty()) { |
| 49 | + int levelSize = planned.size(); |
| 50 | + distance++; |
| 51 | + |
| 52 | + for (int i = 0; i < levelSize; i++) { |
| 53 | + Node current = planned.poll(); |
| 54 | + |
| 55 | + for (int[] dir : directions) { |
| 56 | + int x1 = current.x + dir[0]; |
| 57 | + int y1 = current.y + dir[1]; |
| 58 | + |
| 59 | + if (!isValid(x1, y1, grid) || isWall(grid[x1][y1])) continue; |
| 60 | + |
| 61 | + char next = grid[x1][y1]; |
| 62 | + int mask = current.mask; |
| 63 | + |
| 64 | + // we have no key for this lock, e.g: |
| 65 | + // 1. mask = 1001 -> we have 'a' and 'd' keys |
| 66 | + // 2. lock = 'B' -> 'B' - 'A' = 1 |
| 67 | + // 3. mask >> 1 = 1001 >> 1 = 100 |
| 68 | + // 4. 100 & 1 == 0 -> locked |
| 69 | + if (isLock(next) && (mask >> (next - 'A') & 1) == 0) continue; |
| 70 | + |
| 71 | + // found a key |
| 72 | + if (isKey(next)) { |
| 73 | + mask = mask | (1 << (next - 'a')); |
| 74 | + if (mask == allKeysFoundMask) return distance; |
| 75 | + } |
| 76 | + |
| 77 | + if (visited[x1][y1][mask] == 0) { |
| 78 | + planned.offer(new Node(x1, y1, mask)); |
| 79 | + visited[x1][y1][mask] = 1; |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + return -1; |
| 86 | + } |
| 87 | + |
| 88 | + private boolean isValid(int x, int y, char[][] grid) { |
| 89 | + return x >= 0 && y >= 0 && x < grid.length && y < grid[0].length; |
| 90 | + } |
| 91 | + |
| 92 | + private boolean isKey(char c) { |
| 93 | + return Character.isLowerCase(c); |
| 94 | + } |
| 95 | + |
| 96 | + private boolean isLock(char c) { |
| 97 | + return Character.isUpperCase(c); |
| 98 | + } |
| 99 | + |
| 100 | + private boolean isStart(char c) { |
| 101 | + return c == '@'; |
| 102 | + } |
| 103 | + |
| 104 | + private boolean isWall(char c) { |
| 105 | + return c == '#'; |
| 106 | + } |
| 107 | + |
| 108 | + private char[][] toGrid(String[] lines) { |
| 109 | + char[][] grid = new char[lines.length][lines[0].length()]; |
| 110 | + for (int i = 0; i < lines.length; i++) { |
| 111 | + grid[i] = lines[i].toCharArray(); |
| 112 | + } |
| 113 | + |
| 114 | + return grid; |
| 115 | + } |
| 116 | + |
| 117 | + private record Node(int x, int y, int mask) { |
| 118 | + } |
| 119 | +} |
0 commit comments