LeetCode 353. Design Snake Game

本文介绍了一款Snake游戏的设计与实现细节,包括游戏初始化、移动逻辑处理及边界碰撞检测等核心功能。通过双向链表和无序集合优化了蛇身状态的管理,实现了高效的移动与食物碰撞检测。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Design a Snake game that is played on a device with screen size = width x height. Play the game online if you are not familiar with the game.

The snake is initially positioned at the top left corner (0,0) with length = 1 unit.

You are given a list of food’s positions in row-column order. When a snake eats the food, its length and the game’s score both increase by 1.

Each food appears one by one on the screen. For example, the second food will not appear until the first food was eaten by the snake.

When a food does appear on the screen, it is guaranteed that it will not appear on a block occupied by the snake.

Example:
Given width = 3, height = 2, and food = [[1,2],[0,1]].

Snake snake = new Snake(width, height, food);

Initially the snake appears at position (0,0) and the food at (1,2).

|S| | |
| | |F|

snake.move(“R”); -> Returns 0

| |S| |
| | |F|

snake.move(“D”); -> Returns 0

| | | |
| |S|F|

snake.move(“R”); -> Returns 1 (Snake eats the first food and right after that, the second food appears at (0,1) )

| |F| |
| |S|S|

snake.move(“U”); -> Returns 1

| |F|S|
| | |S|

snake.move(“L”); -> Returns 2 (Snake eats the second food)

| |S|S|
| | |S|

snake.move(“U”); -> Returns -1 (Game over because snake collides with border)

s思路:
1. 这道设计题要求设计Move这个函数。如下图,整个snake每移动一次,新的头部是在之前的头部基础上变化(+/-1),尾部的位置直接去掉,而其他位置不发生变化。因此,根据这个一特点,不用每次都更新所有位置,而只是取头部、尾部。
2. 为方便取头部尾部,我潜意识想到了用list,双向链表,因为每次只需要操作头、尾,且时间o(1)。每次先取头部的坐标和尾部坐标,然后头部坐标根据move的方向修改,同时把尾部坐标删除,然后根据修改后新的头部来判断是否越界,即:cross the border,或bite the body,查询是否bite the body即是判断新的头部的坐标是否和蛇体的坐标重合,这就需要把蛇体的坐标保存,然后做查询,可以保持在vector或unordered_set内,我选用unordered_set的原因是可以o(1)的查询。

移动前:

| | | | |
| | |S|S|
| |S|S|S|
| |S| | |

移动后:

| | |S| |
| | |S|S|
| |S|S|S|
| | | | |

3. 不用list也是可以的,因为只需要访问和修改开头和结尾的位置,这种情况下,我们可以使用deque,deque同样有访问函数.front(),.back(),或操作函数:.push_front(),.pop_front(), .push_back(), .pop_back(),而且都是o(1)。唯一区别是,deque是由vector组成,在中间位置erase和insert时间是o(n),而list仍然是o(1). 但同样的capacity,list因为存了大量指针,所以空间占用打得多!
4. 参考网上的答案,发现很多都不是直接存储每个位置的横纵坐标,而是把坐标转换成一个数存。原因是:存两个数,浪费空间,而且要单独写hash function。这个坐标转换很妙,看下图:

| | | | |
| | |S| |
| | | | |
| | | | |

对于S所在的位置,传统方式就是存(row,col)=(1,2)。高级的方式是,把这个pair映射成一个数,即:head=row*width+col=1*4+2=6。仔细观察,其实就是把整个2d的矩阵转换成一个1维的向量,把所有行连接成一整行即可!再次证明一点,换个角度思考和换个思路看问题很有趣,问题得到了最大程度的还原,思路也不被刻板映像绑架,实在是有意思!

//方法1:用list+unordered_set:list方便o(1)查询并删除
//snake首尾坐标;unordered_set方便o(1)查询是否bite the //snake body.
class SnakeGame {
public:


    /** 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]. */
    SnakeGame(int width, int height, vector<pair<int, int>> food) {
        this->food=food;
        this->width=width;
        this->height=height;
        foodPos=0;
        snake.push_front({0,0})//初始位置
        ss.insert({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. */
    int move(string direction) {
        row=snake.front().first;
        col=snake.front().second;
        pair<int,int> tail=snake.back();
        snake.pop_back();
        if(direction=="U") row--;
        if(direction=="D") row++;
        if(direction=="L") col--;
        if(direction=="R") col++;
        if(row<0||row>=height||col<=||col>=width||ss.count({row,col}))
            return -1;
            //检测是否cross the border or bite the body

        snake.push_front({row,col});
        ss.insert({row,col});
//检查snake的头部是否是food的位置,是的话,
//把snake的尾巴填上,表示尾巴没有移动,只是头移动一步,长度+1,
            snake.push_back(tail);
            ss.insert(tail);
            foodPos++;
        }
        return ss.size()-1;      
    }

private:
    vector<pair<int,int>> food;
    list<pair<int,int>> snake;
    int row,col,width,height;
    int foodPos;
    unordered_set<pair<int,int>,hashpair> ss;
    //自定义unordered_set<pair<int,int>,hashpair>的hashpair
    struct hashpair{
        size_t operator()(const pair<int,int>&x) const{
            return (hash<int>(x.first)<<1)^hash<int>(s.second);
        }
    };  
}; 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值