题目:
你还记得那条风靡全球的贪吃蛇吗?
我们在一个 n*n 的网格上构建了新的迷宫地图,蛇的长度为 2,也就是说它会占去两个单元格。蛇会从左上角((0, 0) 和 (0, 1))开始移动。我们用 0 表示空单元格,用 1 表示障碍物。蛇需要移动到迷宫的右下角((n-1, n-2) 和 (n-1, n-1))。
每次移动,蛇可以这样走:
- 如果没有障碍,则向右移动一个单元格。并仍然保持身体的水平/竖直状态。
- 如果没有障碍,则向下移动一个单元格。并仍然保持身体的水平/竖直状态。
- 如果它处于水平状态并且其下面的两个单元都是空的,就顺时针旋转 90 度。蛇从(
(r, c)、(r, c+1))移动到 ((r, c)、(r+1, c))。
算法:
本身不算难,理清楚6种可能的移动状态,用status表示横竖位置比用另外一个坐标表示蛇首要简洁很多。
方法一:DFS+剪枝
func minimumMoves(grid [][]int) int {
n := len(grid)
ans := math.MaxInt32
// 走到某个位置时及对应的步数
cache := make(map[int]int)
var dfs func(step int, x, y, status int, rotate bool)
dfs = func(step int, x, y, status int, rotate bool) {
if x == n - 1 && y == n - 2 && status == 0 {
ans = min(ans, step)
return
}
// 不剪枝会超时,不能说访问过这个位置,就跳过。而是如果到当前位置有更少的step就跳过。
if 0 < cache[getKey(x, y, status)] && cache[getKey(x, y, status)] <= step {
return
}
cache[getKey(x, y, status)] = step
// 水平右移
if status == 0 && y + 2 < n && grid[x][y + 2] == 0 {
dfs(step + 1, x, y + 1, status, false)
}
// 水平下移
if status == 0 && x + 1 < n && grid[x + 1][y] == 0 && grid[x + 1][y + 1] == 0 {
dfs(step + 1, x + 1, y, status, false)
}
// 水平顺时针旋转
if !rotate && status == 0 && x + 1 < n && grid[x + 1][y] == 0 && grid[x + 1][y + 1] == 0 {
dfs(step + 1, x, y, 1, true)
}
// 竖直右移
if status == 1 && y + 1 < n && grid[x][y + 1] == 0 && grid[x + 1][y + 1] == 0 {
dfs(step + 1, x, y + 1, status, false)
}
// 竖直下移
if status == 1 && x + 2 < n && grid[x + 2][y] == 0 {
dfs(step + 1, x + 1, y, status, false)
}
// 竖直逆时针旋转
if !rotate && status == 1 && y + 1 < n && grid[x][y + 1] == 0 && grid[x + 1][y + 1] == 0 {
dfs(step + 1, x, y, 0, true)
}
}
dfs(0, 0, 0, 0, false)
if ans == math.MaxInt32 {
return -1
}
return ans
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func getKey(x, y, status int) int {
return status * 10000 + x * 100 + y
}
方法二:BFS
BFS每次增加一个step,保证最小达到(n-1,n-2,0)时经过的步数就是结果。
func minimumMoves(grid [][]int) int {
n := len(grid)
visited := make([][][2]int, n)
for i := range visited {
visited[i] = make([][2]int, n)
}
queue := []tuple{tuple{0, 0, 0}}
for step := 0; len(queue) != 0 ; step ++ {
// 如果当前位置已经到达右下角,返回step
length := len(queue)
for i := 0; i < length; i ++ {
x, y, status := queue[i].i, queue[i].j, queue[i].status
if x == n - 1 && y == n - 2 && status == 0 {
return step
}
if visited[x][y][status] == 1{
continue
}
visited[x][y][status] = 1
// 水平右移
if status == 0 && y + 2 < n && grid[x][y + 2] == 0 {
queue = append(queue, tuple{x, y + 1, status})
}
// 水平下移
if status == 0 && x + 1 < n && grid[x + 1][y] == 0 && grid[x + 1][y + 1] == 0 {
queue = append(queue, tuple{x + 1, y, status})
}
// 水平顺时针旋转
if status == 0 && x + 1 < n && grid[x + 1][y] == 0 && grid[x + 1][y + 1] == 0 {
queue = append(queue, tuple{x, y, 1})
}
// 竖直右移
if status == 1 && y + 1 < n && grid[x][y + 1] == 0 && grid[x + 1][y + 1] == 0 {
queue = append(queue, tuple{x, y + 1, status})
}
// 竖直下移
if status == 1 && x + 2 < n && grid[x + 2][y] == 0 {
queue = append(queue, tuple{x + 1, y, status})
}
// 竖直逆时针旋转
if status == 1 && y + 1 < n && grid[x][y + 1] == 0 && grid[x + 1][y + 1] == 0 {
queue = append(queue, tuple{x, y, 0})
}
}
// 走六种方向,如果能走则加入queue
queue = queue[length:]
}
return -1
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
type tuple struct {
i int
j int
status int
}