LeetCode第353题_贪吃蛇游戏

LeetCode 第353题:贪吃蛇游戏

📖 文章摘要

本文详细解析LeetCode第353题"贪吃蛇游戏",这是一道考察数据结构设计和游戏逻辑的中等难度题目。文章提供了基于双端队列和哈希集合的解法,包含C#、Java、Python三种语言实现,配有详细的思路分析和性能对比。适合想要深入理解数据结构和游戏设计的程序员。

核心知识点: 双端队列、哈希集合、游戏设计
难度等级: 中等
推荐人群: 数据结构进阶学习者、游戏开发者

题目描述

请你设计一个贪吃蛇游戏,该游戏将会在一个 屏幕尺寸 = 宽度 x 高度 的屏幕上运行。

起初时,蛇在左上角的 (0, 0) 位置,身体长度为 1 个单位。你将会被给出一个 (行, 列) 形式的食物位置序列。

实现 SnakeGame 类:

  • SnakeGame(int width, int height, int[][] food) 初始化对象,屏幕大小为 height x width,食物位置序列为 food
  • int move(String direction) 返回蛇在移动后的得分。如果蛇吃到食物,得分加一。如果蛇与自己相撞或者撞墙,返回 -1。

示例

输入:
["SnakeGame", "move", "move", "move", "move", "move", "move"]
[[3, 2, [[1, 2], [0, 1]]], ["R"], ["D"], ["R"], ["U"], ["L"], ["U"]]
输出:
[null, 0, 0, 1, 1, 2, -1]

解释:
SnakeGame snakeGame = new SnakeGame(3, 2, [[1, 2], [0, 1]]);
snakeGame.move("R"); // 返回 0
snakeGame.move("D"); // 返回 0
snakeGame.move("R"); // 返回 1,蛇吃掉了第一个食物,位于 (1, 2)
snakeGame.move("U"); // 返回 1
snakeGame.move("L"); // 返回 2,蛇吃掉了第二个食物,位于 (0, 1)
snakeGame.move("U"); // 返回 -1,蛇与边界相撞,游戏结束

提示

  • 1 <= width, height <= 104
  • 1 <= food.length <= 50
  • food[i].length == 2
  • 0 <= food[i][0] < height
  • 0 <= food[i][1] < width
  • direction 字符串为 {"U","D","L","R"} 之一
  • 最多调用 104move 方法

解题思路

本题的核心是使用合适的数据结构来维护蛇的身体位置和移动状态。主要解法如下:

  1. 使用双端队列(Deque)存储蛇的身体

    • 队列头部表示蛇头位置
    • 队列尾部表示蛇尾位置
    • 每次移动时更新队列
  2. 使用哈希集合(HashSet)存储蛇身位置

    • 快速判断是否与自身相撞
    • 维护当前蛇占据的所有格子
  3. 移动逻辑处理:

    • 计算蛇头的下一个位置
    • 检查是否撞墙或自身
    • 判断是否吃到食物
    • 更新蛇的身体位置

代码实现

C# 实现

public class SnakeGame {
    private int width;
    private int height;
    private int[][] food;
    private int foodIndex;
    private int score;
    private LinkedList<(int, int)> snake;
    private HashSet<(int, int)> snakeSet;
    
    public SnakeGame(int width, int height, int[][] food) {
        this.width = width;
        this.height = height;
        this.food = food;
        this.foodIndex = 0;
        this.score = 0;
        this.snake = new LinkedList<(int, int)>();
        this.snakeSet = new HashSet<(int, int)>();
        
        // 初始位置
        snake.AddFirst((0, 0));
        snakeSet.Add((0, 0));
    }
    
    public int Move(string direction) {
        // 获取蛇头位置
        var head = snake.First.Value;
        int newRow = head.Item1;
        int newCol = head.Item2;
        
        // 根据方向移动
        switch (direction) {
            case "U": newRow--; break;
            case "D": newRow++; break;
            case "L": newCol--; break;
            case "R": newCol++; break;
        }
        
        // 检查是否撞墙
        if (newRow < 0 || newRow >= height || newCol < 0 || newCol >= width) {
            return -1;
        }
        
        // 移除蛇尾
        var tail = snake.Last.Value;
        snake.RemoveLast();
        snakeSet.Remove(tail);
        
        // 检查是否撞到自己
        if (snakeSet.Contains((newRow, newCol))) {
            return -1;
        }
        
        // 添加新的蛇头
        snake.AddFirst((newRow, newCol));
        snakeSet.Add((newRow, newCol));
        
        // 检查是否吃到食物
        if (foodIndex < food.Length && 
            newRow == food[foodIndex][0] && 
            newCol == food[foodIndex][1]) {
            // 吃到食物,加回蛇尾
            snake.AddLast(tail);
            snakeSet.Add(tail);
            foodIndex++;
            score++;
        }
        
        return score;
    }
}

Java 实现

class SnakeGame {
    private int width;
    private int height;
    private int[][] food;
    private int foodIndex;
    private int score;
    private Deque<int[]> snake;
    private Set<String> snakeSet;
    
    public SnakeGame(int width, int height, int[][] food) {
        this.width = width;
        this.height = height;
        this.food = food;
        this.foodIndex = 0;
        this.score = 0;
        this.snake = new LinkedList<>();
        this.snakeSet = new HashSet<>();
        
        // 初始位置
        snake.offerFirst(new int[]{0, 0});
        snakeSet.add("0,0");
    }
    
    public int move(String direction) {
        // 获取蛇头位置
        int[] head = snake.peekFirst();
        int newRow = head[0];
        int newCol = head[1];
        
        // 根据方向移动
        switch (direction) {
            case "U": newRow--; break;
            case "D": newRow++; break;
            case "L": newCol--; break;
            case "R": newCol++; break;
        }
        
        // 检查是否撞墙
        if (newRow < 0 || newRow >= height || newCol < 0 || newCol >= width) {
            return -1;
        }
        
        // 移除蛇尾
        int[] tail = snake.pollLast();
        snakeSet.remove(tail[0] + "," + tail[1]);
        
        // 检查是否撞到自己
        String newPos = newRow + "," + newCol;
        if (snakeSet.contains(newPos)) {
            return -1;
        }
        
        // 添加新的蛇头
        snake.offerFirst(new int[]{newRow, newCol});
        snakeSet.add(newPos);
        
        // 检查是否吃到食物
        if (foodIndex < food.length && 
            newRow == food[foodIndex][0] && 
            newCol == food[foodIndex][1]) {
            // 吃到食物,加回蛇尾
            snake.offerLast(tail);
            snakeSet.add(tail[0] + "," + tail[1]);
            foodIndex++;
            score++;
        }
        
        return score;
    }
}

Python 实现

from collections import deque

class SnakeGame:
    def __init__(self, width: int, height: int, food: List[List[int]]):
        self.width = width
        self.height = height
        self.food = food
        self.food_index = 0
        self.score = 0
        self.snake = deque([(0, 0)])  # 蛇的身体位置
        self.snake_set = {(0, 0)}     # 蛇身占据的格子
        
    def move(self, direction: str) -> int:
        # 获取蛇头位置
        head_row, head_col = self.snake[0]
        
        # 根据方向移动
        if direction == "U": head_row -= 1
        elif direction == "D": head_row += 1
        elif direction == "L": head_col -= 1
        else: head_col += 1  # "R"
        
        # 检查是否撞墙
        if (head_row < 0 or head_row >= self.height or 
            head_col < 0 or head_col >= self.width):
            return -1
        
        # 移除蛇尾
        tail = self.snake.pop()
        self.snake_set.remove(tail)
        
        # 检查是否撞到自己
        new_head = (head_row, head_col)
        if new_head in self.snake_set:
            return -1
        
        # 添加新的蛇头
        self.snake.appendleft(new_head)
        self.snake_set.add(new_head)
        
        # 检查是否吃到食物
        if (self.food_index < len(self.food) and 
            head_row == self.food[self.food_index][0] and 
            head_col == self.food[self.food_index][1]):
            # 吃到食物,加回蛇尾
            self.snake.append(tail)
            self.snake_set.add(tail)
            self.food_index += 1
            self.score += 1
            
        return self.score

复杂度分析

时间复杂度

  • 初始化:O(1)
  • 移动操作:O(1)
    • 双端队列的头尾操作都是O(1)
    • 哈希集合的查询和更新也是O(1)

空间复杂度

  • O(N),其中N是蛇的最大长度
    • 双端队列存储蛇的身体位置
    • 哈希集合存储蛇身占据的格子

代码亮点

  1. 🎯 使用双端队列高效管理蛇的身体
  2. 💡 使用哈希集合快速判断碰撞
  3. 🔍 清晰的移动逻辑处理流程
  4. 🎨 代码结构模块化,易于维护

常见错误分析

  1. 🚫 忘记更新蛇尾位置
  2. 🚫 判断自身碰撞的时机错误
  3. 🚫 食物序列索引处理不当
  4. 🚫 边界条件检查不完整

相关题目

解题技巧总结

  1. 使用双端队列维护动态变化的序列
  2. 使用哈希集合优化查找操作
  3. 分步处理复杂的游戏逻辑
  4. 注意边界条件和特殊情况

📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新至第353题。


💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值