【Leetcode】353. Design Snake Game

題目地址:

https://leetcode.com/problems/design-snake-game/

設計二維矩陣內的貪吃蛇遊戲。題目保證蛇在吃了一個食物後,下一個食物不會處於蛇的身體上。要動態返回遊戲的得分,每當蛇吃了一個食物後,身體會變長,並且得分會加11;如果蛇走到了邊界之外,或者走到了自己身上,得分返回1-1

思路是用雙端隊列,隊頭表示蛇尾,隊尾表示蛇頭,這樣每走一步的時候,就可以將新走到的那個格子入隊,並且隊頭出隊。之所以要用雙端隊列,是因爲需要用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;
    }
}

時空複雜度O(d)O(d)dd指direction。

註解:
1、move()方法的最後,如果沒走到食物,一定要先將蛇尾出隊,再判斷是否蛇頭咬到自己,否則會出錯。因爲當蛇走了一步的時候,蛇尾會跟着一起走,即使蛇頭下一步會走到蛇尾原先的地方,但由於蛇尾已經走掉了,此時並不會咬到自己。

2、本題使用雙端隊列是不得已而爲之,如果不把整條蛇的格點全記錄下來的話,即使知道蛇頭會走到哪裏,也很難判斷蛇尾跟到了哪裏,最困難的地方在於不知道蛇尾的下一步的方向。用雙端隊列就很好解決了這個問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章