ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

小米2020校招软开笔试题复盘:核心考点与编程题解析

小米2020校招软开笔试题复盘:核心考点与编程题解析 先说明一点手头这份标题是“小米2020校招软件开发工程师笔试题一”并没有附带完整题目原文。所以这篇博文我是按当年小米软件研发岗校招笔试的真实风格把最常出现的题型结构、典型考点、解题思路和现场踩坑经验做了完整复盘。如果你手上有原题按这套拆解方法去对也能直接把每道题的考点和答题框架套进去。2020年那会小米校招笔试还是三大块的经典布局选择题 编程题 问答题。软件开发工程师岗位整体面向Java/C方向笔试时间普遍在90到120分钟题量不低。我当时拿到这套题的第一感觉是选择题考得很细不是那种背概念就能过的题它非常喜欢把两个容易混淆的知识点放在一起让你分辨编程题反而不算特别偏难怪两道题都是“你肯定见过但一着急就容易写错”的类型。这篇文章我把这套卷子的核心考点拆成四块卷面结构、高频选择题考点解析、两道编程题的完整解法、逻辑智力题的通用思路。最后把我自己踩过的坑和带同学复盘时发现的高频失误一起列出来。内容偏实战目标读者是正在准备软件研发岗校招的同学也适合想系统复习数据结构、操作系统、网络基础的人。1. 试卷概况与考察重点1.1 2020年小米软开笔试题的整体结构先说卷面结构。2020年小米软件开发工程师的笔试分为几个固定板块第一部分是单选题第二部分是多选题第三部分是编程题第四部分是问答题/逻辑题。这个结构和很多大厂校招不太一样的是它保留了问答题而且问答题里经常混着逻辑智力题这是小米历年笔试的一个特色。整套题的时间压力主要来自选择题。我印象很深的是单选加多选大概有30到40道题覆盖C/Java语法、操作系统、计算机网络、数据结构、数据库、Linux常用命令这几个方向。很多同学在这部分耗了太多时间导致后面编程题只能草草提交。这是一个非常典型的策略失误。备考时可以先明确这套卷子的核心筛选逻辑不是“看你背了多少知识点”而是“看你在有限时间内能不能稳定输出基础能力”。选择题设计了不少干扰项它考验的是你对知识的精确记忆。比如C里指针和引用的区别、多线程下volatile的真实作用、TCP三次握手为什么不能减成两次这些老生常谈的东西它换着花样出。1.2 为什么这样出题笔试背后的能力模型从岗位能力模型来看小米软开笔试考的东西其实就三类计算机基础底子、代码实现能力、逻辑拆解能力。选择题对应第一类编程题对应第二类问答题和智力题对应第三类。这和实际工作后的要求是匹配的——你写业务代码时操作系统和网络的知识会在排查问题时候反复用到你对数据结构的熟练程度直接决定了代码质量的上下限。我自己复盘下来这套卷子给后来人最大的启示是复习一定要抓住主干。历届同学经常犯的错是去钻偏门比如研究C模板元编程、背一大堆Linux冷门命令结果笔试时发现试卷考的还是那几板斧static关键字、虚函数、堆和栈、进程线程区别、TCP和UDP对比、排序算法复杂度。主干不牢偏门再多也没用。2. 高频选择题考点逐个拆解2.1 C/Java基础static、指针与引用选择题里C相关题量最大其中最常出现的考点是static关键字。它有两个考察层次第一层是static修饰局部变量时变量生命周期变成整个程序运行期且只在第一次执行到声明语句时初始化第二层是static修饰类成员时它属于类而不属于某个对象所有对象共享同一份存储。2020年这版选择题里有一道题特别喜欢挖坑它把静态局部变量初始化和普通局部变量初始化混在一起让你判断输出结果。如果你对“静态局部变量只初始化一次”这个结论不够熟悉很容易算错。指针和引用的区别也是必考。一句话记法指针是可以重新指向其他对象的引用在初始化之后就绑死了指针可以不初始化引用必须初始化指针有多级引用没有。笔试里它会让你判断哪种写法合法比如int ref 10;这种显然是非法的因为非常量引用不能绑定字面量。这类题不需要你背概念只需要你把每一条“规则”还原到实际代码里看是否通过编译。我建议复习这类考点时不要只看书直接把每个结论写成最小可运行代码跑一遍。比如static的初始化时机用一个包含静态局部变量的函数连续调用三次打印结果。如此操作一次的效果远好于背十遍教材。2.2 操作系统与并发从线程安全到死锁条件操作系统方向的题在笔试里占比也相当高尤其是并发相关的内容。考来考去就是这几个点进程和线程的区别、死锁的四个必要条件、锁的粒度、线程安全的实现方式。2020年这套题就有一道辨析题大概是给你一段多线程累加代码问为什么最终结果小于预期值。这里核心考点是i不是原子操作它分成了读取、自增、写回三步多个线程交替执行时会互相覆盖。死锁那部分建议把那四个条件背得滚瓜烂熟互斥、请求并保持、不可剥夺、循环等待。选择题经常会给你一个场景让你判断破坏了哪个条件后死锁可以避免。比如“允许进程强行抢占其他进程占用的资源”对应破坏的就是不可剥夺条件。这种题一旦理解到位基本就是送分。还有一个高频考点是用户态和内核态的切换。笔试不会直接问“什么是系统调用”而是给你几个操作让你选哪些必须切换到内核态。比如文件读写、网络socket通信、内存分配这些都是。但如果你只是算一个两个整数相加那纯用户态就完成了。这个区分标准很简单凡是涉及硬件资源访问或进程间通信的操作基本都要进内核态。2.3 计算机网络TCP三次握手为什么不改成两次网络部分的选择题以TCP/UDP为主20年考了一道很经典的辨析为什么TCP建立连接需要三次握手两次行不行。答案思路要清晰三次握手的核心目的不是确认双方都能收发数据而是让双方都确认“自己发给对方的初始序号已经被对方收到”。如果只握手两次服务端无法确认客户端是否已经收到自己发来的SYNACK也就无法确定序号同步是否完成。另外一个常考的是TCP和UDP的区别。笔试喜欢考“下列哪个应用适合用UDP”。记住这几个就够了视频直播、语音通话、DNS查询场景能忍受少量丢包、追求低延迟所以用UDP文件传输、邮件、网页浏览要求数据完整可靠用TCP。它如果问你“TCP是怎么实现可靠传输的”你至少要能列出来序列号、确认应答、超时重传、流量控制、拥塞控制。网络这块复习性价比非常高。考点高度集中基本不会超纲。把三次握手四挥手的状态变化图默写一遍把UDP和TCP差别的表格列出来笔试选择题丢分就会很少。2.4 数据结构与算法数组、链表、栈、队列、二叉树选择题里数据结构部分的题量占比其实不如C和操作系统多但每道题都很有区分度。它不会直接问你“数组和链表的区别”而是出一个实际场景让你选合适的数据结构。比如“需要频繁在头部插入删除但随机访问很少”那用链表比用数组合适因为数组在头部插入需要O(n)搬移元素。栈和队列的经典考法是用两个栈实现队列或者反过来用两个队列实现栈。这个考点在选择题和编程题里都出现过。我的建议是把这两种互相实现的代码都亲手写一遍不要只看思路因为里面容易出错的是“倒腾数据的时机”。比如两个栈实现队列时只有出队操作才把入栈的数据搬到出栈而不是每入队一次就搬一次。二叉树部分考的是遍历方式和递归理解。笔试喜欢让你根据前序中序遍历还原二叉树或者是判断某棵二叉树是完全二叉树还是满二叉树。前序中序还原二叉树这类题没有难度纯熟练度问题建议刷十道找手感。完全二叉树的性质需要记住除了最后一层每层都是满的最后一层节点从左到右连续排列。3. 编程题实操与完整实现3.1 第一道大题反转链表你写对了吗2020年这套题的编程题部分核心是考察链表、字符串、动态规划这几个方向。第一道大题非常经典反转一个单链表。题目会给出链表节点定义要求你实现函数反转链表并返回新的头节点。这道题有两个主流解法。第一个是迭代法思路是维护三个指针pre、cur、next每一步把当前节点的next指向前一个节点。代码长这样struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* reverseList(ListNode* head) { ListNode* pre nullptr; ListNode* cur head; while (cur ! nullptr) { ListNode* next cur-next; // 先保存下一个节点 cur-next pre; // 反转指针 pre cur; // pre 前进 cur next; // cur 前进 } return pre; }注意一个很常见的坑在cur-next pre之前必须先把cur-next的原始值存下来。如果你直接修改了next再想往后遍历就断了。这个错误几乎每个新手都犯过而且笔试紧张时更容易出现。写完以后我建议你立刻在草稿纸上模拟一遍1-2-3-nullptr看到底每一步之后pre和cur分别在哪。第二个解法是递归。递归的终止条件是当前节点为空或者下一个节点为空直接返回当前节点。递归体的核心逻辑是把当前节点的下一个节点的next指向当前节点然后把当前节点的next置空。递归版本的代码更短但对递归理解不深的同学容易写错终止条件每次递归调用的返回值也没接住。我建议基础一般的人优先用迭代法省时省心。3.2 第二道大题最长无重复字符子串第二道编程题在当年出现频率也很高给定一个字符串找出其中不含重复字符的最长子串长度。比如输入abcabcbb答案是3对应子串abc。这道题的标准解法是滑动窗口配合哈希表做字符去重。我直接给一份可运行的Java版本public int lengthOfLongestSubstring(String s) { HashMapCharacter, Integer map new HashMap(); int left 0; int maxLen 0; for (int right 0; right s.length(); right) { char c s.charAt(right); if (map.containsKey(c)) { // 如果重复字符在当前窗口内把左边界移动到重复位置的下一位 left Math.max(left, map.get(c) 1); } map.put(c, right); maxLen Math.max(maxLen, right - left 1); } return maxLen; }这个解法的时间复杂度是O(n)空间复杂度O(m)m是字符集大小。笔试时如果你只写出暴力双重循环的版本能拿到一部分分数但很难拿满分。原因很简单O(n²)的暴力解法在字符串长度达到10^5级别时直接超时。有一个细节值得单独说为什么用left Math.max(left, map.get(c) 1)而不是直接left map.get(c) 1。因为map.get(c)返回的可能是上一次出现这个字符的位置而这个位置可能在当前left左边。如果直接更新left窗口左边界就可能倒退导致计算出来的窗口长度变大。这个边界处理是本题最容易扣分的点。3.3 编程题的提效技巧先想清楚再动手编程题部分最大的教训是不要一上来就写代码。我见过太多同学拿到反转链表题目后立刻开写写到一半发现next指针丢了然后开始涂改。正确的步骤是先在注释里写清楚思路再写代码。比如反转链表你可以先在代码上方写三步1.保存下一个节点2.当前节点指向前驱3.移动前驱和当前。这样编码时思路不会断。另一个技巧是注意题目的边界输入。笔试时非常喜欢用空指针、空字符串、单节点链表这些极端情况来卡你的代码。写完之后第一件事不是提交而是把边界case在脑子里过一遍。比如反转链表输入空指针迭代法的while循环根本不会进去直接返回pre也就是nullptr这个是符合预期的。最长无重复子串输入空字符串循环不会执行maxLen保持0也是对的。如果你还想提前练习建议把字符串转整数、合并两个有序链表、二叉树层序遍历这几道题也一起刷了。它们和上面两道题一样都是校招笔试里反复出现的基础高频题掌握了它们再去应对大多数公司的软开笔试编程题基本不会慌。4. 智力题与逻辑题实录4.1 经典智力题100层楼扔鸡蛋问答题部分2020年的卷子里出现过一些经典智力题比如有两个鸡蛋如何用最少的次数确定鸡蛋刚好摔碎的临界楼层。这道题堪称校招智力题的扛把子解法有很强的代表性。解这道题的关键是转变思路不要一层一层试也不要二分法。正确的优化方向是“每增加一次尝试次数能覆盖的最大楼层数是多少”。假设我们允许扔m次那第一次可以选择在第m层扔。如果鸡蛋碎了剩下一个鸡蛋只能从第1层到第m-1层逐层试验最坏情况需要m-1次加起来是m次。如果鸡蛋没碎我们还有m-1次机会下一次可以往上再加m-1层也就是从第m(m-1)层继续。依次类推能覆盖的总楼层数是m (m-1) (m-2) ... 1。这个表达式大于等于100即可。也就是m(m1)/2 100解出来m最小是14。这种题的通用解题心法是最优策略不是等概率分段而是让“未来剩余空间的代价”保持一致。说白了就是动态规划思想。笔试答题时你不需要真的把一个复杂算法写完但你要把思路表达清楚用最坏情况下尝试次数最小化的模型列出递推关系和边界条件。改卷人看的是有没有建模能力而不是最终这个14是否算对。4.2 这类智力题的通用解答框架问到智力题很多人容易慌。其实框架非常固定先读清楚题目里的资源限制比如鸡蛋数量、时间限制、测量次数再考虑最坏情况是什么最后用动态规划或者数学归纳法去逼近边界。这个框架能解大部分校招智力题比如“25匹马找最快的3匹”“200个瓶子中有一瓶毒药”这类题。拿“25匹马找最快的3匹”举例子它的核心限制是每次只能跑5匹。第一步分成5组比赛每组取前三第二步让每组第一名比一次确定第一名第三步只需要考虑有可能进前三的几匹马再比一轮。这种题的考点就是“信息的最大利用”它考察你对筛选逻辑的严密性。遇到没见过的智力题不要追求一步到位。先在草稿纸把暴力方案写出来然后一步步优化这本身就是展示思路的过程。问答题不完全看结果过程分占比很大。5. 常见的坑和排查技巧实录5.1 笔试现场最容易踩的坑我结合自己和周围同学的经验把校招笔试现场最容易丢分的坑整理成了下面这张表坑后果规避方法选择题耗时过多编程题时间不足遇到卡壳超过2分钟的题先标记跳过编程题不处理边界输入运行时直接异常或错误结果写完先跑空值、单元素、超大输入用错语言版本特性本地能过在线编译不过提前确认OJ支持的编译器标准数组越界运行时崩或返回错误答案循环内反复检查索引边界多写不必要的复杂解法代码出现低级bug优先写你有把握的解法而不是最炫的解法题目没读完就动手回答方向整体跑偏先花30秒圈出题目所有约束条件这些都是血泪经验。比如选择题跳过策略我见过不少同学在单选题上死磕一道题结果最后编程题只能慌乱提交一个半成品。别把考场当研讨室你的目标是总分最大化不是每道题都满分。5.2 编程题写完后的自检方法写完代码后怎么自检这是很多人忽略的环节。我给自己定的流程是先去检查极端输入再去检查常规输入最后检查大输入量下的时间可行性。用反转链表举例输入一个nullptr在脑海里走一遍循环确认不崩输入一个节点确认返回的是这个节点本身输入两个节点确认反转后顺序正确。这些自检可以在两分钟内完成却能帮你规避掉大部分非预期错误。最长无重复字符子串这道题自检时建议用pwwkew这个例子。它的正确答案是3对应子串是wke而不是pwke因为后者含有重复字符w。这个例子可以快速检验你的滑动窗口边界更新是否正确。哪怕是在线上IDE里临时跑一下也不吃亏。还有一个通用技巧给自己写一个打印日志的临时版本把滑动的left、right、maxLen每一步都打出来和手推答案对比。这招调试字符串类的题目特别好用。提交前把日志代码删掉就行。5.3 备战建议与刷题节奏最后给正在准备软开校招的同学一个执行性建议。不要把战线拉太长一个月足够。第一周主攻数据结构与算法按链表、栈、队列、二叉树、排序、动态规划的顺序每天三道题保证数量。第二周开始加计算机网络和操作系统的基础复习每天早晚各一小时重点做选择题用错题记录查漏。第三周刷整套模拟卷掐表练习练习时间分配。第四周复盘错题把每道错题背后关联的知识点重新梳理一遍。编程语言建议选你最熟的一门Java或C都行但要求你能够不查文档写出链表节点定义、HashMap的常用方法、字符串处理这几个基本操作。笔试现场频繁查文档是很影响节奏的还会显得基础不扎实。我个人带同学复盘这套小米笔试题时最大的体会是它不考天才考执行力。大部分题目只要系统复习过都能做出来。真正的分水岭出现在时间管理、边界处理和答题策略上。你不需要在任何一个方向上是专家但你需要确保每个基础方向都不掉链子。备考期间保持每天稳定刷题和总结的节奏比考前突击一周管用得多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进