Skip to content

Commit 3441c28

Browse files
committed
added problem 864
1 parent 5062ac4 commit 3441c28

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package problem0864
2+
3+
/*
4+
You are given an m x n grid grid where:
5+
'.' is an empty cell.
6+
'#' is a wall.
7+
'@' is the starting point.
8+
Lowercase letters represent keys.
9+
Uppercase letters represent locks.
10+
You start at the starting point and one move consists of walking one space in one of the four cardinal directions.
11+
You cannot walk outside the grid, or walk into a wall.
12+
If you walk over a key, you can pick it up and you cannot walk over a lock unless you have its corresponding key.
13+
For some 1 <= k <= 6, there is exactly one lowercase and one uppercase letter of the first k letters of the English alphabet in the grid.
14+
This means that there is exactly one key for each lock, and one lock for each key;
15+
and also that the letters used to represent the keys and locks were chosen in the same order as the English alphabet.
16+
Return the lowest number of moves to acquire all keys. If it is impossible, return -1.
17+
*/
18+
19+
var moves = [4][2]int{
20+
{1, 0}, {0, 1}, {-1, 0}, {0, -1},
21+
}
22+
23+
type Path struct {
24+
X, Y int
25+
Keys uint32
26+
}
27+
28+
func shortestPathAllKeys(grid []string) int {
29+
var res int
30+
sx, sy, dst := FindStartAndKeys(grid)
31+
var cur, nxt []Path
32+
cur = []Path{{sx, sy, 0}}
33+
var cache = makeCache(grid)
34+
for len(cur) > 0 {
35+
nxt = nxt[:0]
36+
res++
37+
for _, c := range cur {
38+
if c.Keys == dst {
39+
return res - 2
40+
}
41+
nxt = append(nxt, NextCells(grid, cache, c)...)
42+
}
43+
cur, nxt = nxt, cur
44+
}
45+
return -1
46+
}
47+
48+
func makeCache(grid []string) [][]map[uint32]bool {
49+
var res = make([][]map[uint32]bool, len(grid))
50+
for i := range grid {
51+
res[i] = make([]map[uint32]bool, len(grid[0]))
52+
for j := range grid[i] {
53+
res[i][j] = map[uint32]bool{}
54+
}
55+
}
56+
return res
57+
}
58+
59+
func NextCells(grid []string, cache [][]map[uint32]bool, cur Path) []Path {
60+
var res = make([]Path, 0, 4)
61+
if isOutside(grid, cur.X, cur.Y) {
62+
return nil
63+
}
64+
if cache[cur.X][cur.Y][cur.Keys] {
65+
return nil
66+
}
67+
if grid[cur.X][cur.Y] == '#' {
68+
return nil
69+
}
70+
if isUpper(grid[cur.X][cur.Y]) && !cur.HasKey(grid[cur.X][cur.Y]) {
71+
return nil
72+
}
73+
if isLower(grid[cur.X][cur.Y]) {
74+
cur.AddKey(grid[cur.X][cur.Y])
75+
}
76+
cache[cur.X][cur.Y][cur.Keys] = true
77+
for _, move := range moves {
78+
if !isOutside(grid, cur.X, cur.Y) {
79+
res = append(res, Path{cur.X + move[0], cur.Y + move[1], cur.Keys})
80+
}
81+
}
82+
return res
83+
}
84+
85+
func isOutside(grid []string, x, y int) bool {
86+
return x < 0 || x >= len(grid) || y < 0 || y >= len(grid[0])
87+
}
88+
89+
// FindStartAndKeys finds the starting position and the desired keys
90+
func FindStartAndKeys(grid []string) (int, int, uint32) {
91+
var sx, sy int
92+
var key Path
93+
for i := range grid {
94+
for j := range grid[i] {
95+
switch grid[i][j] {
96+
case '@':
97+
sx, sy = i, j
98+
case '.', '#':
99+
default:
100+
if isLower(grid[i][j]) {
101+
key.AddKey(grid[i][j])
102+
}
103+
}
104+
}
105+
}
106+
return sx, sy, key.Keys
107+
}
108+
109+
func isUpper(letter byte) bool {
110+
return letter >= 'A' && letter <= 'Z'
111+
}
112+
113+
func isLower(letter byte) bool {
114+
return letter >= 'a' && letter <= 'z'
115+
}
116+
117+
func (p *Path) AddKey(key byte) {
118+
p.Keys |= 1 << (key - 'a')
119+
}
120+
121+
func (p *Path) HasKey(lock byte) bool {
122+
var bit uint32 = 1 << (lock - 'A')
123+
return (p.Keys & bit) == bit
124+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package problem0864
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
type TestCase struct {
11+
Grid []string
12+
Expected int
13+
}
14+
15+
var TestCases = []TestCase{
16+
{
17+
[]string{
18+
"@.a..",
19+
"###.#",
20+
"b.A.B",
21+
}, 8,
22+
},
23+
{
24+
[]string{
25+
"@..aA",
26+
"..B#.",
27+
"....b",
28+
}, 6,
29+
},
30+
{
31+
[]string{
32+
"@Aa",
33+
}, -1,
34+
},
35+
}
36+
37+
func TestShortestPathToAllKeys(t *testing.T) {
38+
assert := assert.New(t)
39+
40+
for _, tc := range TestCases {
41+
want := tc.Expected
42+
got := shortestPathAllKeys(tc.Grid)
43+
assert.Equal(want, got, fmt.Sprintf("%+v", tc))
44+
}
45+
}
46+
47+
func BenchmarkShortestPathToAllKeys(b *testing.B) {
48+
for i := 0; i < b.N; i++ {
49+
for _, tc := range TestCases {
50+
shortestPathAllKeys(tc.Grid)
51+
}
52+
}
53+
}

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ Each problem is in it's own directory, with test files. There are helper package
295295
| 0844 | [Backspace String Compare](https://leetcode.com/problems/backspace-string-compare/) | [My Solution](./problems/problem0844) ||
296296
| 0848 | [Shifting Letters](https://leetcode.com/problems/shifting-letters) | [My Solution](./problems/problem0848) ||
297297
| 0858 | [Mirror Reflection](https://leetcode.com/problems/mirror-reflection) | [My Solution](./problems/problem0858) ||
298+
| 0864 | [Shortest Path to Get All Keys](https://leetcode.com/problems/shortest-path-to-get-all-keys) | [My Solution](./problems/problem0864) ||
298299
| 0867 | [Transpose Matrix](https://leetcode.com/problems/transpose-matrix) | [My Solution](./problems/problem0867) ||
299300
| 0869 | [Reordered Power of 2](https://leetcode.com/problems/reordered-power-of-2) | [My Solution](./problems/problem0869) ||
300301
| 0871 | [Minimum Number of Refueling Stops](https://leetcode.com/problems/minimum-number-of-refueling-stops) | [My Solution](./problems/problem0871) ||

0 commit comments

Comments
 (0)