昨晚我本来想早睡的,结果手一贱点开算法题,看到“贪吃蛇”三个字我就笑了……这不就是我本人吗,看到宵夜就“+1格”,看到体重秤就“Game Over”。
贪吃蛇这种题啊,看着像小游戏,其实核心就俩事:蛇身怎么存,撞墙/撞自己怎么判。你要是用数组硬挪,每走一步全体成员搬家,CPU都得跟你一起累出黑眼圈。正常人思路是:蛇头往前长一格,蛇尾(大部分时候)缩一格——所以数据结构天生就该用双端队列 Deque,头插尾删,手感贼对。
然后“撞自己”这个坑,很多人会在队列里遍历找,走一步 O(n),走到最后你会发现不是蛇在吃苹果,是时间在吃你。解决办法也不玄学:再配一个 HashSet 存蛇占用的格子,O(1) 查。唯一需要嘴碎一下:移动时先处理尾巴这件事,别写反了。因为有一种情况:蛇头要走到“蛇尾刚离开的那格”,这是合法的,你要是先判 set 里有就直接判死,恭喜你把自己写成了“贪吃蛇碰瓷版”。
我写个偏“题目通用”的版本:给定网格宽高、食物列表、移动指令,输出每一步分数(吃到就+1),撞墙或撞到自己返回 -1。你面试/刷题基本够用了。
import java.util.*;// 一个很朴素的贪吃蛇模拟器:不画图,只算分数/死亡publicclassSnakeGame{privatefinalint width, height;privatefinalint[][] food;privateint foodIdx = 0;// 蛇身:头在 first,尾在 lastprivate Deque<Integer> body = new ArrayDeque<>();private Set<Integer> occupied = new HashSet<>();// 方向privatestaticfinal Map<Character, int[]> DIR = Map.of('U', newint[]{-1, 0},'D', newint[]{ 1, 0},'L', newint[]{ 0,-1},'R', newint[]{ 0, 1} );publicSnakeGame(int width, int height, int[][] food){this.width = width;this.height = height;this.food = food == null ? newint[0][0] : food;int start = encode(0, 0); body.addFirst(start); occupied.add(start); }// 每走一步,返回当前分数;死了返回 -1publicintmove(char dir){int[] d = DIR.get(dir);if (d == null) thrownew IllegalArgumentException("bad dir: " + dir);int head = body.peekFirst();int hr = head / width, hc = head % width;int nr = hr + d[0], nc = hc + d[1];// 撞墙if (nr < 0 || nr >= height || nc < 0 || nc >= width) return -1;int next = encode(nr, nc);// 先把尾巴“暂时挪走”,避免误判走到尾巴原位置int tail = body.peekLast(); occupied.remove(tail);boolean eat = (foodIdx < food.length && food[foodIdx][0] == nr && food[foodIdx][1] == nc);// 撞自己(注意:尾巴已暂时移除)if (occupied.contains(next)) return -1;// 头进 body.addFirst(next); occupied.add(next);if (eat) {// 吃到:尾巴不动(相当于增长),把尾巴加回占用 occupied.add(tail); body.addLast(tail); foodIdx++; } else {// 没吃到:尾巴就真的走了,不加回 body.pollLast(); }return body.size() - 1; // 分数通常定义为吃到的食物数 }privateintencode(int r, int c){return r * width + c; }// 小测试:你可以自己改 food 和 moves 玩publicstaticvoidmain(String[] args){int[][] food = {{1,2},{0,1}}; SnakeGame g = new SnakeGame(3, 2, food); String moves = "RDRUL";for (char c : moves.toCharArray()) {int score = g.move(c); System.out.println(c + " -> " + score);if (score == -1) break; } }}
你看,整套逻辑其实就像我每天的状态:早上“头进”(打开IDE),晚上“尾删”(关电脑),遇到奶茶就 eat=true,遇到体检就 return -1……行了我不说了,老板喊我开会了,等下我再回来吐槽。