題目地址:
https://leetcode.com/problems/design-snake-game/
設計二維矩陣內的貪吃蛇遊戲。題目保證蛇在吃了一個食物後,下一個食物不會處於蛇的身體上。要動態返回遊戲的得分,每當蛇吃了一個食物後,身體會變長,並且得分會加;如果蛇走到了邊界之外,或者走到了自己身上,得分返回。
思路是用雙端隊列,隊頭表示蛇尾,隊尾表示蛇頭,這樣每走一步的時候,就可以將新走到的那個格子入隊,並且隊頭出隊。之所以要用雙端隊列,是因爲需要用peekLast()
方法看一下蛇頭在什麼位置,才能接着走下一步。同時爲了節省空間和查詢迅速,可以用一個哈希表記錄蛇的身體的各個點的座標。代碼如下:
import java.util.*;
public class SnakeGame {
// 表示二維矩陣的格點
class Pair {
int x, y;
Pair(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object another) {
Pair pair = (Pair) another;
return x == pair.x && y == pair.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
int height, width;
int score;
int[][] food;
// foodIdx表示下一個要吃的食物的座標
int foodIdx;
Deque<Pair> snake;
Set<Pair> track;
/**
* Initialize your data structure here.
*
* @param width - screen width
* @param height - screen height
* @param food - A list of food positions
* E.g food = [[1,1], [1,0]] means the first food is positioned at [1,1], the second is at [1,0].
*/
public SnakeGame(int width, int height, int[][] food) {
this.height = height;
this.width = width;
this.food = food;
foodIdx = 0;
snake = new LinkedList<>();
snake.offer(new Pair(0, 0));
track = new HashSet<>();
track.add(new Pair(0, 0));
}
/**
* Moves the snake.
*
* @param direction - 'U' = Up, 'L' = Left, 'R' = Right, 'D' = Down
* @return The game's score after the move. Return -1 if game over.
* Game over when snake crosses the screen boundary or bites its body.
*/
public int move(String direction) {
char d = direction.charAt(0);
// 取出蛇頭,並計算下一步要到達的位置
int x = snake.peekLast().x, y = snake.peekLast().y;
x += d == 'D' ? 1 : 0;
x += d == 'U' ? -1 : 0;
y += d == 'L' ? -1 : 0;
y += d == 'R' ? 1 : 0;
// 如果下一步越界了,則直接返回-1
if (!inBound(x, y)) {
return -1;
}
// 否則以下一步所到達的位置新建一個格點對象
Pair cur = new Pair(x, y);
// 看看有沒有走到食物上;
// 注意這裏一定要判斷一下還有沒有食物可以吃,如果已經吃完的話,就會造成下標越界
if (foodIdx < food.length) {
if (x == food[foodIdx][0] && y == food[foodIdx][1]) {
// 走到食物上,分數加一,將新的蛇頭入隊也存進哈希表
score++;
snake.offer(cur);
track.add(cur);
foodIdx++;
return score;
}
}
// 沒走到食物,則取出蛇尾,從隊列中出隊,並且從哈希表裏刪去
Pair tail = snake.poll();
track.remove(new Pair(tail.x, tail.y));
// 接着看接下來要走到的那個格點是否是蛇身,如果是,則返回-1
if (track.contains(cur)) {
return -1;
}
// 如果沒走到蛇身,則將新蛇頭入隊,並存進哈希表
snake.offer(cur);
track.add(cur);
// 最後返回當前分數
return score;
}
private boolean inBound(int x, int y) {
return 0 <= x && x < height && 0 <= y && y < width;
}
}
時空複雜度,指direction。
註解:
1、move()
方法的最後,如果沒走到食物,一定要先將蛇尾出隊,再判斷是否蛇頭咬到自己,否則會出錯。因爲當蛇走了一步的時候,蛇尾會跟着一起走,即使蛇頭下一步會走到蛇尾原先的地方,但由於蛇尾已經走掉了,此時並不會咬到自己。
2、本題使用雙端隊列是不得已而爲之,如果不把整條蛇的格點全記錄下來的話,即使知道蛇頭會走到哪裏,也很難判斷蛇尾跟到了哪裏,最困難的地方在於不知道蛇尾的下一步的方向。用雙端隊列就很好解決了這個問題。