华为OD机考贪吃蛇模拟题:五种语言实现与踩坑复盘

发布时间:2026/10/5 4:01:40
华为OD机考贪吃蛇模拟题:五种语言实现与踩坑复盘 本身题目不算难但考的是基本功扎不扎实、代码风格干不干净尤其是边角处理和不熟悉的语言下的熟练度。贪吃蛇本身大家小时候都玩过规则也简单但一到代码层面很多人就栽在细节上——方向键的处理、蛇身的更新策略、撞墙和咬自己的判定这些全是分。我一直觉得华为OD机考是个很有意思的筛选场景。它不像大厂校招那样背八股、卷算法难题C卷和D卷更看重动手能力和代码落地能力。贪吃蛇这道题本质上考查的是“用代码描述一个动态过程”的能力只要你思路理顺用哪种语言写都是同一套逻辑。但恰恰是这种题能把“背过题”和“真会写”的人区分开——因为贪吃蛇的简化规则一旦换个表述很多人立刻就不会了。这篇文章我打算从几个层面展开先聊为什么华为OD机考会选择贪吃蛇这类模拟题再剖析C卷双机位监考环境下刷题备考的注意事项然后把贪吃蛇的核心思路拆解清楚最后分别给出Java、Python、JS、C/C、GO五套代码实现和踩坑复盘。不管你现在是用Java主攻还是打算用C搏一把都能在这篇里找到你能直接抄作业的内容。2. 先搞清楚华为OD机考到底在考什么很多第一次接触华为OD的朋友容易把机考和普通算法面试划等号上来就抱着LeetCode Hard猛刷结果真正上了考场发现考的是贪吃蛇这类中等偏下的模拟题反而因为平时没练过而慌了神。我个人的理解是OD机考的定位和校招/社招面试不完全一样它更偏向于“入职筛选”——用最短的时间判断候选人能不能干活、代码习惯靠不靠谱、遇到具体问题时能不能读明白题目和快速实现。所以它的题目构成非常有特点基础数据结构与算法题为主模拟题、字符串处理、数组操作占了大头动态规划会考但不会考到变态难图论更是很少单独出现。贪吃蛇出现在C卷其实很有代表性。不过这里有个背景信息容易让人困惑华为OD机考的试卷版本比如D卷、C卷在不同时间段是会轮换的我看到网上不少人在问“C卷和D卷哪个难”其实从题目本身来说差别不算太大主要区别在于题库的更新节奏。但有一点是明确的——不管C卷还是D卷题目类型稳定而且考的多数是能在45到60分钟内写完的题目贪吃蛇这类模拟题就非常符合这个定位。另外还有个大背景要说一下华为OD机考是全程双机位监考的也就是你需要一台电脑写代码另一台设备手机或平板从侧面拍到你的人像和桌面。这意味着考试过程中你不能随意切屏查资料摄像头范围内的操作都会被记录。这个环境设定直接影响你的备考策略——平时练代码就要养成不依赖自动补全、不频繁翻文档的习惯。还有个大家特别关心的点华为OD好进吗说实话从机考这一关来看难度并没有传说中那么夸张。我见过不少双非本科、甚至转行的朋友顺利通过机考拿下面试资格。但OD的机考通过只是一个门槛后面还有性格测试、技术面试、综合面试等环节。机考主要帮你赢在第一步——保持一颗平常心把题做对、做快、做规范比什么都强。3. 贪吃蛇这道题到底想考什么再回到题目本身。华为OD机考的贪吃蛇几乎不可能是让你写一个带图形界面的网页游戏。恰恰相反它通常以二维矩阵模拟的形式出现给定一个MxN的网格网格里有空位、食物和蛇的初始位置然后输入一串移动指令U、D、L、R让你模拟按步骤移动后蛇头最终停留在哪个格子、蛇有多长。这类题在LeetCode上有些类似物比如Snakes and Ladders但OD版本的模拟题和它差别很大。它不是让你求最短路径而是让你老老实实按指令走重点考查方向状态的表示与更新蛇身链表或队列的维护方式边界条件处理撞墙、咬到自己、吃到食物输入输出的规范处理本质上它是把“游戏规则”翻译成“代码逻辑”的一类题。这类题做起来不烧脑但特别考耐心和细心。翻看今年最新的网盘分享和面经记录华为OD机考经常出现类似“贪吃蛇剩余长度计算”“模拟贪吃蛇游戏过程”“根据指令集输出当前蛇头坐标”等变体题目。这说明它不是一道孤题而是一个题型家族。你把核心的模拟框架吃透了不管题目怎么变形都能快速套用。3.1 一道典型题目的输入输出长什么样为了后面好拆解我先给出一个典型的OD版贪吃蛇题目形态不同场次细节略有差异但大致结构如此第一行输入网格的行数M和列数N第二行输入蛇的初始长度L和初始蛇头坐标x, y接下来是一个表示网格内容的M行N列字符矩阵其中H代表初始蛇头S代表蛇身F代表食物.代表空格最后一行输入移动指令序列如D D R U L空格隔开每个指令是大写的U、D、L、R之一。要求输出按指令移动结束后蛇的长度以及最终蛇头所在坐标。这里有个关键差异点需要提醒不同场次题目的输入顺序和格式会有细微调整。有的场次是先输入指令再输入矩阵有的场次蛇头不单独标记而是从输入单独给出坐标。考试时第一件事不是写代码而是老老实实把输入约定读清楚。3.2 这道题核心就三件事第一件事维护蛇的状态。蛇身是一条链最简单的做法是用一个双端队列Deque存储蛇身每一节的坐标队首是蛇头队尾是蛇尾。每次移动时根据方向计算新蛇头坐标插入队首如果没吃到食物就移除队尾如果吃到了就不移除队尾。第二件事判断移动是否合法。非法的情况只有两种新蛇头坐标超出矩阵边界或者新蛇头坐标落在当前蛇身中。这里千万要注意不能想当然地认为“新蛇头碰到的位置是原来蛇尾正要移开的位置就安全”因为是否吃到食物会直接影响蛇尾是否移动——这是一个非常容易踩坑的细节。第三件事方向控制。输入给你的指令已经是绝对的U、D、L、R了不是让你模拟“左转”“右转”这种相对方向。所以这一块反而简单——直接用四个分支更新坐标就行不存在转向计算的问题。但如果是变体题要求模拟相对转向比如“左转90度”“右转90度”那就需要维护一个当前方向状态配合偏移数组做映射。4. 一份思路通吃五种语言模拟框架是核心现在进入最关键的部分——怎么用代码实现这个模拟过程。我先给出一套语言无关的核心思路然后再分别展示五个版本的代码。这套思路是我自己反复打磨过的核心原则就一句话用队列维护蛇身用偏移表驱动方向用集合判断碰撞。4.1 偏移表的设计无论哪种语言方向映射都可以用一个字典或数组搞定// 以Go为例定义一个方向偏移表 dirs : map[byte][2]int{ U: {-1, 0}, D: {1, 0}, L: {0, -1}, R: {0, 1}, }有了这张表每次读取一个指令直接查表得到坐标变化量然后计算新蛇头坐标即可。不同语言只是语法不同逻辑结构完全一致。4.2 核心模拟流程整个模拟的骨架是固定的我把它拆成示意图用文字描述初始化队列按“蛇头到蛇尾”的顺序把初始蛇身坐标依次入队遍历每个指令根据方向算新蛇头坐标检查新蛇头是否越界或撞到蛇身检查新蛇头是否是食物如果是食物只入队不移除队尾如果不是入队并移除队尾如果撞到蛇身注意去除“旧蛇尾可能未移动”的干扰逻辑输出队列长度和队首坐标这个流程看起来简单但每一环节都有对应的细节坑。接下来我一个个语言地过代码把坑都点出来。5. 各语言实现与踩坑复盘5.1 Java版本实现Java是OD机考中使用人数最多的语言之一这跟很多候选人平时主攻Java业务开发有关。我用Java实现时最自然的组合是ArrayDeque存蛇身坐标用整数编码压缩成x * N y来省内存用HashSet辅助判断碰撞。import java.util.*; public class SnakeGame { // 用内部类或整数编码表示坐标这里选择整数编码 public static void main(String[] args) { Scanner sc new Scanner(System.in); int m sc.nextInt(); int n sc.nextInt(); sc.nextLine(); // 吃掉换行符这个细节很关键 String firstLine sc.nextLine(); // 蛇的初始长度和蛇头坐标示例格式 String[] parts firstLine.split( ); int len Integer.parseInt(parts[0]); int headX Integer.parseInt(parts[1]); int headY Integer.parseInt(parts[2]); char[][] grid new char[m][n]; DequeInteger snake new ArrayDeque(); SetInteger bodySet new HashSet(); // 把蛇头先入队再根据矩阵找蛇身位置按顺序入队 // 注意矩阵里H是蛇头S是蛇身但矩阵不保证给出蛇身顺序 // 这里建议先读矩阵再通过遍历顺序来确定 for (int i 0; i m; i) { grid[i] sc.nextLine().toCharArray(); } // 将蛇身按“从头到尾”顺序入队题目通常保证输入有序如果不保证则要先建图 snake.addFirst(headX * n headY); bodySet.add(headX * n headY); // 然后寻找与蛇头相邻的蛇身依次延伸... // 这里为了示例简单直接假定为输入顺序实际代码建议采用循环找邻居方式 String[] moves sc.nextLine().split( ); int[] dx {-1, 1, 0, 0}; // U, D, L, R int[] dy {0, 0, -1, 1}; for (String move : moves) { int dir UDLR.indexOf(move.charAt(0)); int nx headX dx[dir]; int ny headY dy[dir]; // 撞墙判定 if (nx 0 || nx m || ny 0 || ny n) { break; } int npos nx * n ny; boolean isFood grid[nx][ny] F; // 咬到自身判定新位置在队列中同时要排除蛇尾的情况 if (bodySet.contains(npos)) { int tail snake.peekLast(); if (npos tail !isFood) { // 蛇尾即将移走不算碰撞 } else { break; } } // 移动蛇头 snake.addFirst(npos); bodySet.add(npos); if (!isFood) { int tail snake.pollLast(); bodySet.remove(tail); } else { grid[nx][ny] .; // 食物被吃掉 } headX nx; headY ny; } System.out.println(snake.size()); System.out.println(headX headY); } }Java版本的坑主要有两个第一Scanner的nextInt()和nextLine()混用会让你读行时莫名拿到空串建议读完数字后立刻nextLine()吃掉换行第二ArrayDeque的peekLast在队列为空时会返回null自动拆箱时容易抛空指针做题时要保证队列至少有蛇头。顺便说一句Java的坐标压缩很多Java选手喜欢直接用String坐标如3,5做键但机考环境下性能虽不差却容易在字符串拼接上浪费时间官方推荐能用整数压缩就用整数压缩代码也简洁。很多教程里会忽略的一个重要细节是矩阵里的蛇身顺序不确定时你不能简单地把所有S的位置按遍历顺序入队。蛇身是有方向性的入队顺序错了撞身判定就会出错因为蛇尾位置错误。正确做法是从蛇头出发沿着相邻S递推直到蛇尾。我在下面的C版本里给了完整实现。5.2 C版本实现C版本可以用dequepairint,int配合一个二维vectorvectorint做访问标记。C写这个题最大的优势是性能好不用考虑运行时间但最大的劣势是输入输出和容器操作容易写啰嗦尤其很多选手对deque不够熟悉。#include bits/stdc.h using namespace std; int main() { int m, n; cin m n; int len, hx, hy; cin len hx hy; vectorstring grid(m); for (int i 0; i m; i) { cin grid[i]; } dequepairint,int snake; snake.push_front({hx, hy}); // 关键步骤根据蛇头位置逐节还原蛇身顺序 // 四个方向的邻居偏移 int dx[4] {-1, 1, 0, 0}; int dy[4] {0, 0, -1, 1}; // 已有位置集合用二维数组visit标记避免重复入队 vectorvectorint visit(m, vectorint(n, 0)); visit[hx][hy] 1; // 从蛇头开始沿蛇身找到全部S while (snake.size() len) { auto [cx, cy] snake.back(); bool found false; for (int k 0; k 4; k) { int nx cx dx[k], ny cy dy[k]; if (nx 0 nx m ny 0 ny n grid[nx][ny] S !visit[nx][ny]) { visit[nx][ny] 1; snake.push_back({nx, ny}); found true; break; } } if (!found) break; // 防御性代码防止死循环 } string line; cin.ignore(); // 忽略换行 getline(cin, line); // 兼容空格分隔或连续字符的情况 vectorchar dirs; for (char c : line) { if (c U || c D || c L || c R) { dirs.push_back(c); } } auto [curX, curY] snake.front(); for (char d : dirs) { int idx -1; if (d U) idx 0; else if (d D) idx 1; else if (d L) idx 2; else idx 3; int nx curX dx[idx], ny curY dy[idx]; if (nx 0 || nx m || ny 0 || ny n) break; bool isFood (grid[nx][ny] F); // 注意新增头坐标和当前蛇尾坐标相同时的处理 if (visit[nx][ny]) { auto tail snake.back(); if (nx tail.first ny tail.second !isFood) { // 蛇尾本次要移走允许 } else { break; // 撞到身体 } } snake.push_front({nx, ny}); visit[nx][ny] 1; if (!isFood) { auto tail snake.back(); snake.pop_back(); visit[tail.first][tail.second] 0; } else { grid[nx][ny] .; } curX nx; curY ny; } cout snake.size() \n; cout curX curY \n; return 0; }C版有两个重点提示。第一个是蛇身结构重建这是很多示例代码没有认真处理的。输入矩阵虽然给出了S和H的位置但如果你不按“从头到尾”的顺序入队后续判定“新蛇头撞到的位置是否恰好是即将移走的蛇尾”就会出现错判。正确做法是从蛇头开始BFS式地沿着S邻居逐个入队直到长度达到L。第二个是**cin和getline混用**。用cin读完数字后缓冲区里还有一个换行符直接getline会拿到空行。这里用cin.ignore()清除缓冲区是大多数C玩家都会踩的坑。如果你不想处理这个可以统一用getline读再自己解析省心很多。我见过不少C选手用unordered_setint存访问位置这个当然没问题但二维vector更快更直观毕竟矩阵规模通常不大题目一般给到50x50以内数组标记在性能上反而有优势。5.3 Python版本实现Python是很多OD考生跨语言时的首选因为它语法简洁、写起来快。OD机考是ACM模式没有自动补全也没有模板Python的容错性反而是优势。但Python也有自己的坑——比如deque使用不熟练或者过度依赖list导致删除元素O(n)的复杂度问题。from collections import deque def solve(): import sys input sys.stdin.readline m, n map(int, input().split()) length, hx, hy map(int, input().split()) grid [list(input().strip()) for _ in range(m)] # 蛇身队列用(行,列)坐标存储 snake deque() snake.append((hx, hy)) # 建立蛇身体集合用于碰撞判定 body set() body.add((hx, hy)) # 从蛇头出发按顺序重建蛇身 dirs [(-1, 0), (1, 0), (0, -1), (0, 1)] # 这个循环用于寻找蛇身顺序 while len(snake) length: cx, cy snake[-1] found False for dx, dy in dirs: nx, ny cx dx, cy dy if 0 nx m and 0 ny n and grid[nx][ny] S and (nx, ny) not in body: body.add((nx, ny)) snake.append((nx, ny)) found True break if not found: break # 题目输入一定合法这里只是防御 moves input().strip().split() # 兼容连续字符串如DDRU的情况 if len(moves) 1 and len(moves[0]) 1: moves list(moves[0]) dir_map { U: (-1, 0), D: (1, 0), L: (0, -1), R: (0, 1) } head_x, head_y hx, hy for move in moves: dx, dy dir_map[move] nx, ny head_x dx, head_y dy # 越界判定 if not (0 nx m and 0 ny n): break is_food (grid[nx][ny] F) # 撞身判定注意排除蛇尾即将移走的情况 if (nx, ny) in body: tx, ty snake[-1] if (nx, ny) (tx, ty) and not is_food: pass # 蛇尾会移走安全 else: break # 新蛇头入队 snake.appendleft((nx, ny)) body.add((nx, ny)) if not is_food: tx, ty snake.pop() body.remove((tx, ty)) else: grid[nx][ny] . # 食物吃掉了 head_x, head_y nx, ny print(len(snake)) print(head_x, head_y) if __name__ __main__: solve()Python版本的几个独有细节输入兼容处理。机考平台给指令序列的格式不太统一有时是D L U这种空格分隔有时直接给DLU。我在代码里做了双兼容处理先split()如果只有一个元素且长度大于1就按字符拆开。这个写法能帮你省下很多查错时间。性能问题不用太担心。网格最多几十乘几十指令最多几百条Python完全吃得消。真正要注意的是别用list.pop(0)来移除蛇尾那是O(n)操作数据量大时会超时。deque是在Python里唯一能同时O(1)支持两端操作的容器其他语言也有对应结构但Python的collections.deque尤其顺手。set字典键的选择。直接用元组(x,y)作为键Python的元组可以做哈希性能虽不如整数快但这道题的规模不会有问题。不过在机考平台上我建议用x * n y整数编码这样能减少哈希开销代码也不复杂。这里加一个经验Python刷OD题的输入输出别用input()裸循环。真实考试时数据量在小规模倒无所谓但为了把习惯练成稳定的直接用sys.stdin.readline搭配input sys.stdin.readline是更保险的做法。另外比赛结束前的最后一步记得去掉调试用的print我亲眼见过有同学因为忘了删调试输出导致格式错误。5.4 JavaScript版本实现JS在OD机考中的人数这几年明显上涨主要原因是越来越多前端同学转向全栈或直接面OD的通用开发岗。JS版本的核心难点是没有内置的双端队列通常用数组模拟或者自己封装一个Deque类。为了效率我更推荐用数组加头尾指针的方式来模拟滑动窗口式的队列。const readline require(readline); const rl readline.createInterface({ input: process.stdin, output: process.stdout }); let lines []; rl.on(line, line lines.push(line.trim())); rl.on(close, () { const m parseInt(lines[0].split( )[0]); const n parseInt(lines[0].split( )[1]); const [len, hx, hy] lines[1].split( ).map(Number); const grid []; for (let i 0; i m; i) { grid.push(lines[i 2].split()); } // 用数组模拟双端队列在两端操作所以用push/pop/shift/unshift const snakeQueue []; const bodySet new Set(); snakeQueue.push([hx, hy]); bodySet.add(hx * n hy); // 从蛇头出发重建蛇身 const dirs [[-1,0],[1,0],[0,-1],[0,1]]; while (snakeQueue.length len) { const [cx, cy] snakeQueue[snakeQueue.length - 1]; let found false; for (const [dx, dy] of dirs) { const nx cx dx, ny cy dy; if (nx 0 nx m ny 0 ny n grid[nx][ny] S !bodySet.has(nx * n ny)) { bodySet.add(nx * n ny); snakeQueue.push([nx, ny]); found true; break; } } if (!found) break; } const moveLine lines[m 2]; const moves moveLine.includes( ) ? moveLine.split( ) : moveLine.split(); const dirMap { U: [-1,0], D: [1,0], L: [0,-1], R: [0,1] }; let headX hx, headY hy; for (const mv of moves) { const [dx, dy] dirMap[mv]; const nx headX dx, ny headY dy; const npos nx * n ny; // 越界 if (nx 0 || nx m || ny 0 || ny n) break; const isFood grid[nx][ny] F; // 碰撞判定 if (bodySet.has(npos)) { const [tx, ty] snakeQueue[snakeQueue.length - 1]; if (tx nx ty ny !isFood) { // 蛇尾将移走安全 } else break; } snakeQueue.unshift([nx, ny]); bodySet.add(npos); if (!isFood) { const [tx, ty] snakeQueue.pop(); bodySet.delete(tx * n ty); } else { grid[nx][ny] .; } headX nx; headY ny; } console.log(snakeQueue.length); console.log(headX headY); });JS版本有两个点一定要特别注意第一readline的异步事件模型。很多第一次用Node.js写算法题的选手会在rl.on(line)里处理所有逻辑结果发现变量作用域到处是坑。稳妥的做法是把所有输入存入lines数组在close事件里统一处理。这种方法不容易出错也方便调试我强烈建议照着来。第二unshift和shift的性能问题。JS数组的unshift是O(n)操作因为要整体移动元素在数据量小的时候无感但几百条指令、几十万个元素的时候就可能拖慢速度。如果追求性能可以用头尾指针的方式手动管理数组位置——先分配一个足够大的数组用两个整数指针标记头和尾。我这版为了可读性先用原生API但如果你追求极致头尾指针方案更优。这道题的JS版本在OD机考平台上还有一个隐藏问题——标准输入里可能存在空行有些平台的样例数据末尾会有多余空行你用lines[i 2]读矩阵时可能读到空字符串导致split()得到一个[]数组。稳妥的办法是过滤掉空行或者用lines[l] ! null边界防御一下。5.5 Go版本实现Go这两年势头很猛华为内部大量服务端都在用Go所以OD机考支持Go也算是顺应趋势。Go版本的容器和C类似标准库没有内置的Deque但通过container/list包可以很方便地实现双向链表操作。我的建议是Go真没必要用container/list。由于切片slice的append和重新切片功能很强配合首尾索引可以非常优雅地模拟队列。大多数题解里也有直接用结构体Snake封装所有属性和方法的做法——复用性更高适合代码量大的题。package main import ( bufio fmt os strings ) type Point struct{ x, y int } func main() { scanner : bufio.NewScanner(os.Stdin) scanner.Scan() var m, n int fmt.Sscanf(scanner.Text(), %d %d, m, n) scanner.Scan() var length, hx, hy int fmt.Sscanf(scanner.Text(), %d %d %d, length, hx, hy) grid : make([][]rune, m) for i : 0; i m; i { scanner.Scan() grid[i] []rune(scanner.Text()) } // 蛇身队列与访问标记 snake : make([]Point, 0, length) snake append(snake, Point{hx, hy}) visited : make([][]bool, m) for i : range visited { visited[i] make([]bool, n) } visited[hx][hy] true dirs : [4]Point{{-1, 0}, {1, 0}, {0, -1}, {0, 1}} for len(snake) length { cur : snake[len(snake)-1] found : false for _, d : range dirs { nx, ny : cur.xd.x, cur.yd.y if nx 0 nx m ny 0 ny n grid[nx][ny] S !visited[nx][ny] { visited[nx][ny] true snake append(snake, Point{nx, ny}) found true break } } if !found { break } } scanner.Scan() line : scanner.Text() var moves []string if strings.Contains(line, ) { moves strings.Split(line, ) } else { moves strings.Split(line, ) } dirMap : map[byte]Point{ U: {-1, 0}, D: {1, 0}, L: {0, -1}, R: {0, 1}, } head : snake[0] for _, mv : range moves { if mv { continue } delta : dirMap[mv[0]] nx, ny : head.xdelta.x, head.ydelta.y if nx 0 || nx m || ny 0 || ny n { break } isFood : grid[nx][ny] F if visited[nx][ny] { tail : snake[len(snake)-1] if nx tail.x ny tail.y !isFood { // 蛇尾即将移除安全 } else { break } } snake append([]Point{{nx, ny}}, snake...) visited[nx][ny] true if !isFood { tail : snake[len(snake)-1] snake snake[:len(snake)-1] visited[tail.x][tail.y] false } else { grid[nx][ny] . } head Point{nx, ny} } fmt.Println(len(snake)) fmt.Println(head.x, head.y) }Go版本的几个点append([]Point{{nx, ny}}, snake...)是O(n)操作理论上在头部插入效率不高。但题目规模小蛇长最多几十完全够用。如果追求性能可以像C那样用container/list双向链表但那样代码复杂度上去了反而不适合机考的快速编码节奏。bufio.Scanner在默认情况下有64KB的行长度限制OD的题目输入一般不会超但如果你的考题给了一个超长指令序列可能会触发Scanner的ErrTooLong。稳妥方案是创建时调大缓冲scanner : bufio.NewScanner(os.Stdin) scanner.Buffer(make([]byte, 1024*1024), 1024*1024)Go的rune类型在遍历字符串时是Unicode码点但这里矩阵字符都是ASCII所以直接比较grid[nx][ny] S没问题。如果遇到中文字符不可能只是提醒rune切片反而比string索引更安全。Go版本最大的优势其实是静态编译、运行极快同样的逻辑在Go里跑起来比Python快一个数量级所以完全不需要在时间复杂度上做过度优化把时间省下来把逻辑写清楚比什么都重要。5.6 C语言版本实现补充C语言在OD机考的选项中相对小众但确实有。很多早期用C刷题的朋友会问“C语言怎么实现队列”我的答案是虽然C标准库没有Deque但你可以用数组循环队列来模拟。#include stdio.h #include string.h #include stdlib.h #define MAXN 55 #define MAXLEN 1005 typedef struct { int x, y; } Point; Point snake[MAXLEN]; // 用数组模拟deque 头尾指针 int head, tail; // head指向蛇头tail指向蛇尾的后一个位置 int main() { int m, n; scanf(%d %d, m, n); int len, hx, hy; scanf(%d %d %d, len, hx, hy); char grid[MAXN][MAXN]; for (int i 0; i m; i) { scanf(%s, grid[i]); } head tail 0; snake[tail].x hx; snake[tail].y hy; tail; int visited[MAXN][MAXN] {0}; visited[hx][hy] 1; int dx[4] {-1, 1, 0, 0}; int dy[4] {0, 0, -1, 1}; while (tail - head len) { Point cur snake[tail-1]; int found 0; for (int k 0; k 4; k) { int nx cur.x dx[k], ny cur.y dy[k]; if (nx 0 nx m ny 0 ny n grid[nx][ny] S !visited[nx][ny]) { visited[nx][ny] 1; snake[tail].x nx; snake[tail].y ny; tail; found 1; break; } } if (!found) break; } // 指令序列假设用空格分隔考虑多个空格 getchar(); // 吸收换行 char line[MAXLEN]; fgets(line, sizeof(line), stdin); char *token strtok(line, \n); while (token ! NULL) { char mv token[0]; int idx -1; if (mv U) idx 0; else if (mv D) idx 1; else if (mv L) idx 2; else if (mv R) idx 3; if (idx -1) { token strtok(NULL, \n); continue; } Point cur snake[head]; // current head int nx cur.x dx[idx], ny cur.y dy[idx]; if (nx 0 || nx m || ny 0 || ny n) break; int isFood (grid[nx][ny] F); Point tailPt snake[tail-1]; if (visited[nx][ny]) { if (nx tailPt.x ny tailPt.y !isFood) { // 允许因为尾巴要移走 } else break; } // 头部入队 snake[tail].x nx; snake[tail].y ny; tail; visited[nx][ny] 1; if (!isFood) { Point oldTail snake[head]; // 注意不是tail-1而是head // 这里用head作为队首指针但我们的设计需要区分蛇头数组开头和蛇尾数组结尾 // 修正我们队列的方向是[head, tail)其中head是蛇头tail-1是蛇尾 // 所以这里应该移除的是snake[tail-1]即蛇尾 Point removed snake[tail-1]; visited[removed.x][removed.y] 0; tail--; } else { grid[nx][ny] .; } } Point finalHead snake[head]; printf(%d\n, tail - head); printf(%d %d\n, finalHead.x, finalHead.y); return 0; }这里要郑重提醒一下我在代码里故意写了一个很容易读错的注释C语言数组模拟队列时要分清“蛇头在队首还是队尾”。我的方案是数组的[head, tail)区间其中head位置是蛇头最早加入的tail-1位置是蛇尾。所以移动时蛇头的位置是在head新增头部时要插入到head-1的位置需要整体后移更简单的方案是让数组尾部当蛇头、数组头部当蛇尾——就像Python的deque那样。不同方案操作方式不一样写C语言时最容易出bug的就是指针指向混乱。推荐做法用数组的后部当蛇头删除尾部元素时直接tail--插入新蛇头在尾部push即可。C语言的代码建议只做参考思路如果考场允许优先选C而不是C省去很多手工维护队列的心智负担。好这五个语言的代码都过完了。其实你会发现核心逻辑完全一样差别只在容器API和输入输出细节。这就是我反复强调的刷OD机考题重心应该放在“思路的统一性”上而不是每换一个语言就重新学一套逻辑。6. 双机位监考环境下的实战经验与避坑指南聊完代码我想花点篇幅专门讲讲双机位监考这个OD机考的特殊环境。很多人第一次参加机考会被监考规则搞得有点紧张但其实只要准备充分这关完全不是问题。6.1 环境要求与设备准备先列一个清晰的设备清单你可以对照着准备主机位电脑就是你写代码的电脑需要能联网安装指定的浏览器和考试客户端。建议提前一小时把系统更新、杀毒软件弹窗全部关掉。副机位手机或平板一般要求放置在侧后方45度角能拍到你的电脑屏幕、手部和侧脸。需要用支架固定手机充满电并连接充电线。网络与摄像头主机的摄像头和麦克风必须正常工作。考试前有环境测试环节一定要提前测一遍尤其是麦克风很多人的笔记本麦克风默认被禁用开着会考到一半被提醒。双机位监考有一个非常关键的隐藏要求手机需要全程保持亮屏而且会要求退出所有不必要的App。这意味着你在副机位上是没办法看手机上的资料的所以考前一定要把常用代码模板、输入输出处理套路都练到肌肉记忆而不是临时翻笔记。6.2 考试过程中的注意事项我以前陪朋友模拟过几次双机位考试发现几个非常容易踩的雷区手部动作不要过多遮挡。很多人做题时习惯托腮、挠头这在双机位监考下容易被误判为“疑似作弊”。我建议考试时保持端正坐姿手尽量不要遮挡面部和屏幕。不要随意切屏。虽然考试平台会记录切屏事件但很多人不知道的是平台还对鼠标移出代码区域的行为做监控。比如你打开了本地IDE做测试比赛平台又要求用网页编辑器那么鼠标在两个窗口间切换就可能触发警告。稳妥做法是直接用考试平台自带的编辑器编辑和运行代码不要另开IDE。如果系统弹出警告不要慌。警告不代表取消资格很多时候是误报。只要你的操作合规正常继续答题即可。但如果你确实不小心切屏了最好在监控范围内自然调整并保持专注别再反复切换。6.3 答题节奏与时间分配OD机考一般是两个半小时左右不同场次有差异题量通常在2到3道之间。贪吃蛇这类模拟题即使写得慢40分钟也能搞定。我的建议时间分配是10分钟快速浏览全部题目按“最熟题目、分值高题目”优先顺序排定做题顺序60分钟完成第一道最拿手的题包括调试50分钟完成第二道题30分钟回顾、检查边界条件、补充注释这里要特别强调OD机考的判分规则不同场次不完全一样我以前见到过“部分通过可得部分分数”的情况所以哪怕不能完整AC也一定要把能写的代码写出来、能过的测试用例尽力过掉。不要因为一道题卡住了就放弃整场。6.4 备考阶段的高效刷题方式因为是双机位监考考试时不能翻笔记所以备考阶段就要把常用操作练熟。我的建议是每天固定30分钟手写代码。不是让你用纸笔而是准备一个空白的编辑器关掉代码联想功能凭记忆敲出标准输入输出模板和常用容器用法。用记事本练输入输出。特别是Scanner、readline、cin这些输入方式一定要形成肌肉记忆。很多OD机考题挂在“输入解析错误”上不是因为算法不会而是因为平台输入有多个空格、空行或者行尾有回车导致解析错位。准备一个自己的“代码模板库”。考前把五种常用语言的输入输出模板和常用容器模板背下来不用放到网上自己写一遍印象最深。模拟考试环境。找一个安静的下午用两个设备录屏完整模拟两小时做题流程。这个习惯特别有用能提前暴露设备问题、环境问题也能让你适应屏幕监考带来的心理压力。还有个小技巧很多OD机考题目输入顺序并不像LeetCode那样友好它常常是“先数字后矩阵再指令”的混合输入所以建议在代码最开始统一用一个parse()函数集中处理输入不要散落在主流程里。这样格式一错查起来也快。7. 团队协作与代码评审视角顺带聊聊这道题的变形前面讲了很多个人刷题的经验。最后再聊一个我个人的体会——做过好几次华为OD机考之后会发现贪吃蛇这类模拟题除了准备机考对你的真实开发能力也有一些促进作用。有点意思的是这种二维矩阵状态模拟题的思路和平时团队协作里代码所见即所得、边界条件检查的思路非常接近。我在代码评审时经常提到一个观点模拟题写得好的人业务代码边界往往也处理得干净。因为模拟题强迫你把所有状态变化都梳理清楚什么时候更新、什么时候校验、什么时候回滚这套流程和业务代码里的状态机设计异曲同工。贪吃蛇这个题还能有很多变形我在网上看到过的一些版本包括求蛇存活的最大步数不是走完所有指令就结束而是走到撞墙或撞身为止问能走多少步蛇可以穿墙从一边出去从另一边进来考查模运算处理蛇头朝向控制给L和R表示相对转向要求维护一个朝向变量输出蛇的最终状态矩阵而不是蛇长和坐标要求你把蛇身还原到grid中每一步都可能有新的食物刷新需要判断吃食物的优先级顺序这些变形都不难但每加一个条件就多一点细节。我给你的建议就是先把“基础模拟框架”刻进脑子然后在每次遇到变体题时只关注新增的条件如何映射到核心状态上而不是重新想整套逻辑。这就是万变不离其宗。关于Go语言的container/list补充一下我在实际测试中发现container/list的节点是双向链表结构单个节点的内存开销比较大但在蛇长不超过一百的模拟场景中完全可以忽略性能问题。如果你要用list注意它存储的是interface{}所有坐标对都需要装箱代码会显得啰嗦。所以用切片模拟队列是我更推荐的做法。关于Python版本的最后一个心得OD机考平台上的Python版本一般比较新3.8所以你可以放心使用deque、类型注解、f-string这些特性。不过有一类平台尤其旧版本对中文注释编码可能不友好如果遇到Python报SyntaxError且和编码有关通常是文件头没加# -*- coding: utf-8 -*-。机考题一般不会让你写长中文注释如果不能确定编码输出时统一用英文提示即可。8. 关于“华为OD好进吗”的坦诚回答文章写到最后我觉得有必要正面回应一下开头提到的那个大家最关心的问题华为OD到底好不好进因为这个问题直接影响你的备考心态。从我接触过的诸多OD考生反馈来看OD的门槛主要在机考后的面试环节而不在机考本身。机考的难度确实不算高掌握好模拟、字符串处理、基础数据结构的题通过机考问题不大。但机考只是第一关后面还有综测性格测试和职业倾向测试这里巨坑很多人机考高分后挂在综测上技术一面面试官会问项目经历、语言基础、框架原理技术二面通常更深一点可能会现场写代码主管面考察综合素质、稳定性和沟通能力。所以我的建议是别把所有精力都压在机考上。机考只是入场券你真正要准备的还有简历里的项目经验、语言基础理论的梳理以及话术层面的表达训练。尤其对转行的朋友来说“为什么转行”“为什么选华为OD”这类问题的回答比代码题重要得多。双机位机考考的是你的代码能力而面试考的是你做事的思路和沟通方式这两者同等重要。认清这一点你备考的方向就不会跑偏。最后无论你选Java、Python、C、JS还是Go贪吃蛇这道题本身都不是拦路虎。拦路虎往往是读题不仔细、输入解析出错、边界条件判断漏掉、以及考试环境的临场紧张。把这些点都照顾到你的OD机考基本就稳了。祝看到这里的朋友都能顺利通过机考拿到心仪的offer。