|
3 | 3 | // #Hard #Breadth_First_Search #Bit_Manipulation
|
4 | 4 |
|
5 | 5 | import java.util.LinkedList;
|
| 6 | +import java.util.Queue; |
6 | 7 |
|
| 8 | +@SuppressWarnings("java:S135") |
7 | 9 | public class Solution {
|
8 | | - class Status { |
9 | | - int key; |
10 | | - int i; |
11 | | - int j; |
| 10 | + private int m; |
| 11 | + private int n; |
12 | 12 |
|
13 | | - public Status(int key, int i, int j) { |
14 | | - this.key = key; |
15 | | - this.i = i; |
16 | | - this.j = j; |
| 13 | + public int shortestPathAllKeys(String[] stringGrid) { |
| 14 | + // strategy: BFS + masking |
| 15 | + m = stringGrid.length; |
| 16 | + n = stringGrid[0].length(); |
| 17 | + char[][] grid = new char[m][n]; |
| 18 | + int index = 0; |
| 19 | + // convert to char Array |
| 20 | + for (String s : stringGrid) { |
| 21 | + grid[index++] = s.toCharArray(); |
17 | 22 | }
|
18 | | - } |
19 | | - |
20 | | - public int shortestPathAllKeys(String[] grid) { |
21 | | - int success = 0; |
22 | | - int startI = 0; |
23 | | - int startJ = 0; |
24 | | - int rows = grid.length; |
25 | | - int cols = grid[0].length(); |
26 | | - for (int i = 0; i < rows; i++) { |
27 | | - for (int j = 0; j < cols; j++) { |
28 | | - char c = grid[i].charAt(j); |
29 | | - if (c >= 'A' && c <= 'F') { |
30 | | - success |= 1 << (c - 'A'); |
31 | | - } |
32 | | - |
33 | | - if (c == '@') { |
34 | | - startI = i; |
35 | | - startJ = j; |
| 23 | + // number of keys |
| 24 | + int count = 0; |
| 25 | + Queue<int[]> q = new LinkedList<>(); |
| 26 | + for (int i = 0; i < m; i++) { |
| 27 | + for (int j = 0; j < n; j++) { |
| 28 | + // find starting position |
| 29 | + if (grid[i][j] == '@') { |
| 30 | + q.add(new int[] {i, j, 0}); |
36 | 31 | }
|
37 | | - } |
38 | | - } |
39 | | - int[][][] dist = new int[1 << 6][rows][cols]; |
40 | | - for (int i = 0; i < dist.length; i++) { |
41 | | - for (int j = 0; j < dist[0].length; j++) { |
42 | | - for (int k = 0; k < dist[0][0].length; k++) { |
43 | | - dist[i][j][k] = Integer.MAX_VALUE; |
| 32 | + // count number of keys |
| 33 | + if ('a' <= grid[i][j] && grid[i][j] <= 'f') { |
| 34 | + count++; |
44 | 35 | }
|
45 | 36 | }
|
46 | 37 | }
|
47 | | - LinkedList<Status> queue = new LinkedList<>(); |
48 | | - queue.offer(new Status(0, startI, startJ)); |
49 | | - dist[0][startI][startJ] = 0; |
50 | | - int path = 0; |
51 | | - int[][] dirs = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; |
52 | | - while (!queue.isEmpty()) { |
53 | | - int size = queue.size(); |
54 | | - while (size-- > 0) { |
55 | | - Status status = queue.poll(); |
56 | | - int key = status.key; |
57 | | - int x = status.i; |
58 | | - int y = status.j; |
59 | | - if (key == success) { |
60 | | - return path; |
| 38 | + int[] dx = {-1, 0, 1, 0}; |
| 39 | + int[] dy = {0, -1, 0, 1}; |
| 40 | + // this is the amt of keys we need |
| 41 | + int target = (1 << count) - 1; |
| 42 | + // keep track of position and current state |
| 43 | + boolean[][][] visited = new boolean[m][n][target + 1]; |
| 44 | + // set initial position and state to true |
| 45 | + visited[q.peek()[0]][q.peek()[1]][0] = true; |
| 46 | + int steps = 0; |
| 47 | + while (!q.isEmpty()) { |
| 48 | + // use size to make sure everything is on one level |
| 49 | + int size = q.size(); |
| 50 | + while (--size >= 0) { |
| 51 | + int[] curr = q.poll(); |
| 52 | + int x = curr[0]; |
| 53 | + int y = curr[1]; |
| 54 | + int state = curr[2]; |
| 55 | + // found all keys |
| 56 | + if (state == target) { |
| 57 | + return steps; |
61 | 58 | }
|
62 | | - for (int[] dir : dirs) { |
63 | | - int xx = x + dir[0]; |
64 | | - int yy = y + dir[1]; |
65 | | - if (xx >= 0 |
66 | | - && xx < rows |
67 | | - && yy >= 0 |
68 | | - && yy < cols |
69 | | - && grid[xx].charAt(yy) != '#') { |
70 | | - int nextKey = key; |
71 | | - char c = grid[xx].charAt(yy); |
72 | | - if (c >= 'a' && c <= 'f') { |
73 | | - nextKey = key | (1 << (c - 'a')); |
74 | | - } |
75 | | - |
76 | | - if (c >= 'A' && c <= 'F' && (nextKey & (1 << (c - 'A'))) == 0) { |
77 | | - continue; |
78 | | - } |
79 | | - if (path + 1 < dist[nextKey][xx][yy]) { |
80 | | - dist[nextKey][xx][yy] = path + 1; |
81 | | - queue.offer(new Status(nextKey, xx, yy)); |
82 | | - } |
| 59 | + for (int i = 0; i < 4; i++) { |
| 60 | + int nx = x + dx[i]; |
| 61 | + int ny = y + dy[i]; |
| 62 | + // use new state so we don't mess up current state |
| 63 | + int nState = state; |
| 64 | + // out of bounds or reached wall |
| 65 | + if (!inBounds(nx, ny) || grid[nx][ny] == '#') { |
| 66 | + continue; |
| 67 | + } |
| 68 | + // found key |
| 69 | + // use OR to add key to our current state because if we already had the key the |
| 70 | + // digit would still be 1/true |
| 71 | + if ('a' <= grid[nx][ny] && grid[nx][ny] <= 'f') { |
| 72 | + // bit mask our found key |
| 73 | + nState = state | (1 << (grid[nx][ny] - 'a')); |
| 74 | + } |
| 75 | + // found lock |
| 76 | + // use & to see if we have the key |
| 77 | + // 0 means that the digit we are looking at is 0 |
| 78 | + // need a 1 at the digit spot which means there is a key there |
| 79 | + if ('A' <= grid[nx][ny] |
| 80 | + && grid[nx][ny] <= 'F' |
| 81 | + && ((nState & (1 << (grid[nx][ny] - 'A'))) == 0)) { |
| 82 | + continue; |
| 83 | + } |
| 84 | + // not seen before |
| 85 | + if (!visited[nx][ny][nState]) { |
| 86 | + q.add(new int[] {nx, ny, nState}); |
| 87 | + visited[nx][ny][nState] = true; |
83 | 88 | }
|
84 | 89 | }
|
85 | 90 | }
|
86 | | - path++; |
| 91 | + steps++; |
87 | 92 | }
|
88 | 93 | return -1;
|
89 | 94 | }
|
| 95 | + |
| 96 | + private boolean inBounds(int x, int y) { |
| 97 | + return x >= 0 && x < m && y >= 0 && y < n; |
| 98 | + } |
90 | 99 | }
|
0 commit comments