题目描述
给定一个 N*M 的二维数组 arr,模拟贪吃蛇游戏。初始时:
arr[i][j] == 'H' 表示蛇头起始位置;
arr[i][j] == 'F' 表示食物;
arr[i][j] == 'E' 表示空格。
蛇每次移动一格,若吃到食物(移动到 F),长度加一;若撞到边界或自身,游戏结束。求最终蛇的长度或判定游戏结束条件。
核心解题思路
数据结构选择
使用 队列 或 链表 存储蛇身坐标,队列头部为蛇头,尾部为蛇尾,便于动态增长和移动。
二维数组记录地图状态,实时更新蛇头和食物的位置。
移动逻辑
根据移动方向(上、下、左、右)更新蛇头坐标。
每次移动后检查碰撞:
边界碰撞:蛇头超出地图范围;
自身碰撞:蛇头与队列中其他坐标重复。
食物处理
若新蛇头位置为 F,不删除队尾(长度加一),并生成新食物;
否则,删除队尾(保持长度不变)。
import java.util.*;
public class SnakeGame {
private int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 上、下、左、右
private Deque<int[]> snake = new LinkedList<>();
private Set<String> body = new HashSet<>(); // 快速判断碰撞
private char[][] grid;
private int foodCount = 0;
private int n, m;
public int simulateSnake(char[][] arr, String moves) {
n = arr.length;
m = arr[0].length;
grid = arr;
// 初始化蛇头位置
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (arr[i][j] == 'H') {
snake.add(new int[]{i, j});
body.add(i + "," + j);
break;
}
}
}
// 处理移动指令
for (char move : moves.toCharArray()) {
int[] head = snake.peekFirst();
int dir = getDir(move);
int nx = head[0] + dirs[dir][0];
int ny = head[1] + dirs[dir][1];
// 边界碰撞检查
if (nx < 0 || nx >= n || ny < 0 || ny >= m) {
return -1; // 游戏结束
}
// 自身碰撞检查(注意排除尾部)
String newPos = nx + "," + ny;
if (body.contains(newPos) && !(nx == snake.peekLast()[0] && ny == snake.peekLast()[1])) {
return -1;
}
// 处理食物或移动
if (grid[nx][ny] == 'F') {
foodCount++;
grid[nx][ny] = 'E'; // 食物被吃掉
} else {
int[] tail = snake.pollLast();
body.remove(tail[0] + "," + tail[1]);
}
// 更新蛇头和身体
snake.addFirst(new int[]{nx, ny});
body.add(newPos);
}
return foodCount + 1; // 初始长度为1
}
private int getDir(char c) {
switch (c) {
case 'U': return 0;
case 'D': return 1;
case 'L': return 2;
case 'R': return 3;
default: return -1;
}
}
}
代码关键点解析
数据结构
Deque 实现队列存储蛇身,支持快速头部插入和尾部删除;
HashSet 存储蛇身坐标字符串,用于 O(1) 时间复杂度的碰撞检测。
移动逻辑
遍历移动指令字符串,依次更新蛇头位置;
每次移动后更新队列和 HashSet,动态维护蛇身。
边界与碰撞处理
直接通过坐标范围判断边界碰撞;
自身碰撞需排除尾部(尾部即将被删除,不会与新头碰撞)。
注意
时间复杂度:使用 HashSet 优化碰撞检测,避免遍历整个队列;
初始长度:起始时蛇长度为1(仅包含 H)。