大家应该都有体会,秋招笔试是所有环节里最考验心态的一环。你在电脑前坐着,摄像头开着,屏幕上的倒计时一秒一秒跳,面前是几十道题,每一道都决定你接下来有没有面试机会。2022年小米秋招笔试-软件开发-卷2这套题,我后来复盘了很久,发现它的考点分布非常典型,基础题占了六成,算法题考得比较常规,没有特别偏门的陷阱,但正因为“常规”,反而更能筛出基础扎实的人。
这篇内容适合两类人看:一类是正在备战秋招、准备冲大厂软件研发岗的应届生,另一类是工作几年想跳槽、但没系统刷过校招题的在职开发。干货主要集中在考点拆解、真题还原和应试策略,我不打算讲太多玄乎的“速成方法论”,而是实打实地把卷子的结构、每一类题的破题思路、编程题的完整解法和我们最容易踩的坑讲清楚。看完你至少能对照自己的复习盲区,少走半个月弯路。
1. 卷面整体扫描:题型、题量、时间怎么分配
1.1 题型构成与分值分布
先说结论,这套卷子的整体结构是:单选、多选、编程题三大块。根据我当时参加笔试后和几个同学的交叉回忆,题型和大致分值如下:
| 题型 | 题量 | 分值占比 | 考察重心 |
|---|---|---|---|
| 单选题 | 25题 | 约 40% | 数据结构、操作系统、计算机网络、语言基础 |
| 多选题 | 10题 | 约 25% | 深层次原理、边界概念、代码输出题 |
| 编程题 | 2题 | 约 35% | 算法设计与编码能力,以中等难度为主 |
时间我记得是 90 分钟,但不同批次可能略有不同,有些批次是 120 分钟。这个时间分配很关键,很多人栽就栽在时间上——单选题花太久,编程题留不够,最后两道题只能写个半成品。
从分值结构就能看出来,这套卷子不是“算法决定论”,选择题占了六成以上。换句话说,就算你算法题只过了一道,只要选择题正确率够高,照样能进面试。反过来,如果你忙着刷 LeetCode 难题,把计算机网络和操作系统的基础概念丢在一边,那才是最危险的。
1.2 难度梯度与命题风格
这套卷子的难度梯度设计得很明显。前 10 道单选题基本是送分题,考的是“进程和线程的区别”“TCP 和 UDP 的区别”这种基础中的基础;中间 10 道开始上强度,会涉及缓存替换算法、二叉树遍历序列推导、数据库索引结构这类需要理解记忆的内容;最后 5 道单选和多选才是真正拉分的,比如多线程并发输出顺序、内存对齐、动态多态底层原理等。
编程题的难度曲线也很有代表性。第一题通常是标准的 LeetCode 中等偏下难度,比如滑动窗口、双指针或简单动态规划;第二题稍微难一点,需要结合二叉树或图,但不会到竞赛难度。这和很多大厂的命题思路一致:编程题不是要你秀奇技淫巧,而是看你能不能把常见算法写对、写完整、考虑到边界情况。
1.3 实战时间分配建议
我根据自己的血泪教训,给你一个相对合理的时间分配方案:
- 先做编程题,控制在 40 分钟以内。为什么?因为编程题分值高、思考量大,放在最后容易被时间压力搞崩心态。先做编程题,你脑子是清醒的,写完两道题之后心里有底,再做选择题会从容很多。
- 单选题控制在 25 分钟左右。前 10 道基础题要快,遇到不会的先标记跳过,别恋战。
- 多选题放在最后,留 15-20 分钟。多选题少选得部分分、错选不得分,这种情况下“宁缺毋滥”,不确定的选项不要选。
- 最后留 5 分钟检查:编程题有没有拼写错误、有没有漏掉 return、选择题有没有填错位。
我见过太多人按顺序做题,结果选择题耗了 50 分钟,编程题只剩 20 分钟,第一题写了个大概、第二题直接空着交卷。所以“先做编程题”这个策略,我强烈建议你平时就养成习惯。
2. 选择题考点拆解:从基础到进阶,一道一道看
2.1 数据结构:栈、队列、树和排序,不能只会背定义
这套卷子的数据结构题,考查方式很灵活。不会直接问你“栈的特点是什么”,而是给你一段入栈出栈序列,让你判断哪个出栈顺序是不可能的。这就需要你真正理解栈这种数据结构的工作过程,而不是背概念。
我记得有一道印象很深的题目:给出一棵二叉树的前序遍历和中序遍历序列,要求还原出后序遍历。这类题目我建议你用“三步法”来做:第一步,前序序列第一个元素一定是根节点;第二步,在中序序列里找到这个根节点,根节点左边是左子树、右边是右子树;第三步,递归地对左右子树重复这个过程。画图是关键,别心算,心算容易乱。
还有一个高频考点是排序算法的稳定性。选择题喜欢这样问:下列排序算法中,哪些是稳定的?正确答案应该是插入排序、冒泡排序、归并排序、基数排序。而快速排序、堆排序、希尔排序、选择排序都是不稳定的。这里我有一个记忆技巧:稳定排序的口诀是“插冒归基”,不稳定的是“快些选堆”(快速、希尔、选择、堆排序)。记住这个口诀,这种题三秒出答案。
对于 hash 表和冲突处理,卷子里也有涉及。链地址法和开放定址法的区别要搞清楚:链地址法是将冲突的元素用链表串起来,适合冲突频繁的情况;开放定址法是在表内找空位,适合表较空的情况。选择题还喜欢问装填因子越大,查找效率是越高还是越低——装填因子越大,冲突概率越大,效率越低。
2.2 操作系统:进程线程、调度算法和内存管理
操作系统这部分,占比不低,而且考得很细。进程和线程的区别几乎是必考的,但出题方式会绕一下,比如问“在 Linux 下,进程和线程的哪个资源是共享的”。答案是地址空间和文件描述符表是共享的,而每个线程独立的资源是栈和寄存器。这个考点想清楚了,再遇到“线程切换为什么比进程切换开销小”就能答得出来——因为线程共享地址空间,不需要切换页表。
内存管理里,分页和分段的概念是重点。选择题会问“分页系统的地址结构由哪两部分组成”——页号 + 页内偏移量。还会延伸到缺页中断、页面置换算法,其中最常考的是 LRU 和 FIFO。如果给出一个引用串,让你用 LRU 计算缺页次数,千万别光背概念,要自己动手模拟几遍页面状态表,这个必须练熟。
死锁部分考的是四个必要条件:互斥、持有并等待、不可剥夺、循环等待。这道题通常是多选,四个条件全选才是对的。注意死锁四个条件缺一不可,不是满足一个就会死锁。银行家算法偶尔也会出现,它的核心是“系统在分配资源前先判断分配后是否处于安全状态”,这个逻辑理解一遍就行,不需要背案例。
进程调度算法也是一个稳定的出题点。短作业优先、时间片轮转、优先级调度这几种要能区分,尤其是“时间片轮转法”在交互式系统中的应用,以及“短作业优先”虽然平均等待时间最短,但可能导致长作业饥饿。这些权衡过程,选择题喜欢放在选项里做干扰项。
2.3 计算机网络:TCP、UDP、HTTP 和 DNS 选路
网络部分,卷子的考法偏向基础概念,但多选题容易挖坑。TCP 和 UDP 的区别题,四个选项里通常会有两个明显的正确项,另外两个需要你仔细看:TCP 面向连接、提供可靠传输、支持拥塞控制;UDP 无连接、不保证可靠、首部开销小。如果说“UDP 一定比 TCP 快”,这个选项就是错的,因为网络性能还受链路带宽、延迟等影响,不能简单地这么下结论。
HTTP 的状态码也是高频考点。选择题可能问“404 表示什么”——资源未找到;“503”表示——服务暂不可用。建议你把 2xx、3xx、4xx、5xx 的大类含义记清楚,特别是 301(永久重定向)和 302(临时重定向)的区别,这道题我印象里考过。
三次握手和四次挥手的过程,属于大厂笔试的“规定动作”。三次握手是为了确认双方的收发能力都正常,四次挥手是因为 TCP 是全双工的,关闭连接时需要两个方向都完成关闭。选择题可能会问“SYN 攻击发生在哪个阶段”——就是三次握手的第二步,服务器发送了 SYN-ACK 但收不到确认,导致半连接队列被占满。
另外,DNS 解析的完整流程、从浏览器输入 URL 到页面加载都涉及哪些协议,这两个题出成多选题的可能性很高,大家可以把整个链路串一遍:DNS(域名解析)→ TCP(建立连接)→ TLS(加密握手)→ HTTP(请求响应)。这样串起来记,比单独记每个协议要牢固得多。
2.4 编程语言基础:C++ 内存布局和输出题
卷子里 C++ 相关的题非常多,这也符合软件开发岗的定位,毕竟很多底层岗位都要求 C++ 功底。比较稳定的一类是“代码输出题”,给你一段程序,问输出结果。这种题表面上考输出,实则考的是对语言机制的理解。
内存对齐是我印象很深刻的一个考点。题目给一个结构体,问它占用多少字节。我举一个典型的例子:
struct Example { char a; // 1 字节 int b; // 4 字节 char c; // 1 字节 };很多人直接算 1 + 4 + 1 = 6 个字节,答案错。按照内存对齐规则,char 占 1 字节,int 按 4 字节对齐,所以整体大小是 12 字节(在 64 位系统下),因为编译器会在成员之间填充 padding 以保证性能。还记得当时考场里一个大佬同学说,遇到这个题他直接用编译器跑了一遍,但笔试不允许,所以平时就要养成手算对齐后的结构体大小的习惯。
虚函数和多态也是一个必考方向。选择题会问“虚函数表(vtable)存放在哪里”,这个真的很多人答错。正确答案是:虚函数表指针(vptr)在对象内存布局的开头,而虚函数表本身存放在只读数据段(.rodata),不是放在栈上,也不是放在堆上。这道题如果没理解清楚,很容易被“对象里存的是虚函数表”这个干扰项带偏。
Java 方向的题在这套卷子里也占了一部分,主要考垃圾回收、HashMap 的底层结构和并发编程。问到 HashMap 在 JDK 1.8 中当链表长度大于 8 时转换成红黑树,是为了解决链表过长导致的查询效率下降问题,这是高频考点。线程池的拒绝策略也要看一遍,AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy 四个策略的区别,多选概率很高。
3. 编程题详解:两道真题的完整解题思路
3.1 第一题:最长连续不重复子串(滑动窗口)
这道题是典型的“滑动窗口”问题,LeetCode 第 3 题的原型。题目大意是给一个字符串 s,找出其中不含有重复字符的最长子串的长度,例如输入 "abcabcbb",输出 3。
解题思路倒是不复杂,用双指针维护一个窗口,窗口内保证没有重复字符。右指针不断向右扩大窗口,每当遇到一个新字符,就检查窗口里有没有这个字符。有的话就把左指针移动到重复字符的下一个位置,然后更新最大长度。
我直接给出一个参考实现:
#include <iostream> #include <string> #include <unordered_map> #include <algorithm> using namespace std; int lengthOfLongestSubstring(string s) { unordered_map<char, int> lastPos; // 记录每个字符最近出现的下标 int left = 0; int maxLen = 0; for (int right = 0; right < s.length(); right++) { char ch = s[right]; if (lastPos.count(ch) && lastPos[ch] >= left) { // 如果当前字符在窗口内出现过,移动左指针 left = lastPos[ch] + 1; } lastPos[ch] = right; maxLen = max(maxLen, right - left + 1); } return maxLen; }这个解法的关键都在那个if判断条件里:为什么要加lastPos[ch] >= left这个条件?因为哈希表里记录的是“这个字符最近一次出现的位置”,如果最近一次出现在窗口左侧之外,说明当前窗口里没有这个重复字符,就不需要移动左指针。这是一个非常容易写漏的细节。
时间复杂度和空间复杂度都是 O(n),对这道题完全够用。你如果对自己要求高一点,还可以用数组替代 unordered_map,因为字符集最多 128 或 256 个,用数组更快:
int lastPos[128]; fill(begin(lastPos), end(lastPos), -1);这种做法笔试时跑起来更稳,不会因为哈希函数造成常数开销。
3.2 第二题:二叉树的最近公共祖先
这道题是 LeetCode 第 236 题的原型,在二叉树里找两个节点的最近公共祖先。题目会给一棵二叉树和两个节点 p、q,要求返回它们的最近公共祖先节点。
思路是这样:在二叉树中找节点,先把问题拆开看。当前节点如果是 p 或 q,就直接返回当前节点;否则分别在左子树和右子树里递归查找。左子树找到一个目标,右子树也找到一个目标,说明当前节点就是最近公共祖先。如果只在左子树找到了,就返回左子树的结果;只在右子树找到了,就返回右子树的结果。
放一个我笔试时写的版本:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if (root == nullptr || root == p || root == q) { return root; } TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q); if (left != nullptr && right != nullptr) { return root; } return left != nullptr ? left : right; }这个实现很短,但每一步都有含义。第一个if是递归的出口,同时也是“当前节点就是目标节点”的兜底判断。后面左右子树返回值的情况,实际上是在自底向上地汇总信息。整个递归过程走一遍之后,最近公共祖先就浮上来了。
这种题型写起来容易,但笔试时要注意三点:
- 树节点需要
#include相关的头文件吗?在线笔试平台一般已经内置了结构体定义,你只需要写函数体。 - 注意递归的边界条件,不要把空指针的 val 拿出来用,不然运行时报错会扣很多分。
- 建议写完递归函数后,自己手动在草稿纸上画一个 5 层左右的二叉树,把路径走一遍,确认逻辑没有大的漏洞。
3.3 编程题踩坑记录与提交技巧
编程题虽然思路不难,但提交时的坑比想象中多。我先说几个真实的翻车案例,都是我或者身边同学遇到过的。
先说第一个:函数签名和题目给的不一致。有些平台已经给你定义好了函数原型和参数类型,你只需要在指定区域写实现。但如果你自己额外加了一个main函数,或者改了参数名称以外的类型,编译直接失败。当时有个同学把TreeNode*写成了Tree*,编译器直接报 undefined,白白浪费了 10 分钟找问题。
第二个坑是边界条件。最长不重复子串这道题,如果输入是空字符串,返回 0。如果你忘记了长度判空,测试用例里有一个空串,结果直接把下标访问错了,程序崩掉。很多在线笔试平台会隐藏测试用例,空输入、单字符输入、全重复输入都是常客。写完代码后一定要自己构造几个边界用例跑一遍。
第三个坑是死循环。滑动窗口的左指针移动逻辑写错,右指针无限扩展,程序超时。这种问题不会报编译错误,也不会报运行时错误,而是直接显示“运行超时”。如果你在本地调试时发现输出卡住了,先检查指针移动逻辑,这是最常见的原因。
最后,我对编程题提交的一个个人建议是:先写出一个“能跑”的版本,再考虑优化。哪怕你的解法是 O(n^2) 的暴力法,只要用例不超大,也能先拿一部分分。先把暴力代码提交一次拿到底分,再优化成 O(n) 版本提交第二次,这种策略在笔试中非常实用。平台的评分通常取最高分,你完全可以用暴力版本保底。
4. 从卷面反推备考策略:给当年和未来的秋招人
4.1 按时间线拆解复习计划
这套卷子不只是一套题,它其实代表了很大一类大厂软件开发岗笔试的出题偏好。如果你现在是大三下学期或者研二,距离秋招还有 6 到 8 个月,我建议你把复习拆成四个阶段:
| 阶段 | 时间 | 重点内容 | 产出目标 |
|---|---|---|---|
| 基础夯实 | 第 1-2 个月 | 数据结构与算法、计算机网络、操作系统课本 | 能不看笔记回答 80% 基础概念题 |
| 刷题强化 | 第 3-4 个月 | LeetCode 热题 HOT 100 + 剑指 Offer | 每天 3-5 题,练出手感 |
| 专项突破 | 第 5 个月 | 语言基础、数据库、多线程、设计模式 | 复盘易错点,整理错题本 |
| 真卷模拟 | 第 6 个月 | 牛客网历年真题、大厂模拟笔试 | 卡时间做题,适应现场节奏 |
这个计划看着不复杂,难在坚持。基础夯实阶段最容易被忽视,但你这套卷子的选择题能不能拿高分,完全取决于这个阶段扎不扎实。很多人一上来就刷 LeetCode,刷到后面发现操作系统和网络全忘了,这等于地基没打牢就开始盖楼。
如果你现在离笔试只剩一个月,那就把重心放在真题和错题上。把 LeetCode 热题 HOT 100 里 easy 和 medium 的部分过一遍,至少保证高频题型的模板代码能默写出来——滑动窗口、双指针、二分查找、DFS、BFS、简单 DP、二叉树遍历、并查集,这几类务必熟练。
4.2 大多数人都会踩的 4 个坑
根据我身边秋招同学和后来学弟学妹的反馈,我发现备考笔试有几个高频问题,在这里集中拿出来说一说。
第一个坑是“只刷题,不总结”。我见过一个很努力的学弟,刷了 300 多道 LeetCode,但笔试成绩并不理想。原因很简单,他刷题都是看题解刷的,看完觉得自己懂了,实际上让他独立写还是写不出来。刷题的正确姿势是:先独立思考 30 分钟,想不出来再看题解,然后合上书自己重写一遍。写完后在笔记里标记这道题属于哪个分类,跟哪些题是同一类解法,这样才能形成“题型思维”。
第二个坑是“语言基础依赖 IDE”。笔试环境可不像平时开发那么友好,有些平台的代码编辑器连自动补全都没有,更别说运行调试了。平时练习一定要用白板/记事本写代码,或者至少关掉编辑器的自动补全功能,锻炼手写完整代码的能力。不然考场上连 std::unordered_map 都拼不对,就很尴尬。
第三个坑是“不重视选择题”。我发现算法题刷得多的人,很容易轻视选择题,觉得“背背就行”。但实际上这套卷子选择题占 60% 以上,而且多选的评分规则很残酷——错选一个零分。操作系统、计算机网络这种需要大量记忆的科目,必须拿出整块时间专门背,不能靠临时抱佛脚。
第四个坑是“没有模拟过笔试环境”。秋招笔试基本都是在线限时,有摄像头监控,可能还有切屏警告。你平时在家里安静地刷题,和考场上开着摄像头、听着室友说话、盯着倒计时,完全是两种状态。建议你在牛客网或其他平台上做至少 3 套完整的模拟真题,卡着时间做,感受一下那个节奏。
4.3 笔试过程中的应试策略
这些策略不是让你作弊,而是教你怎么在规则内拿到更高分数。
先说多选题。多选的评分规则通常是:全部选对得满分,少选得部分分,错选不得分。所以在做多选题时,除非你非常确定某个选项正确,否则不要选。例如一个题你确定 A 和 B 对,C 有点印象但拿不准,这时候就只选 A、B,拿牢这 2 分,比冒险多选一个 C 最后错选得 0 分要划算得多。
再说选择题的“排除法”。有些概念题你背得不熟,但通过排除两个明显错误的选项,能大幅提高正确率。比如题目问“以下哪个不属于进程间通信方式”,你只要知道 A 是管道、B 是共享内存、D 是消息队列,剩下 C 就算看不出来,也能推断出来。选择题的干扰项往往设置得很没有水平,先排除掉两个,剩下二选一,胜率明显上升。
编程题的提交策略前面也说过了,先暴力拿底分,再优化拿高分。这里额外提醒一条:如果题目要求的是“返回结果”,你 print 出来也没用,必须用函数返回值。很多在线笔试平台只检查最终函数返回的结果,以及代码是否通过隐藏用例。
检查阶段不要只盯着自己做的题,还要检查“有没有漏题”。有些套题是多选题,题目下面有几个空,页面滚动时很容易误触跳过。最后的检查阶段第一件事就是回到题目列表,把所有题目都标记为“已作答”。
4.4 除了刷题,还有什么值得投入
笔试只是秋招的第一步,过了笔试还有两轮到三轮的技术面试。但你不需要等笔试结果出来才准备面试,笔试完的第二天就该开始准备八股文和项目复盘。
这套卷子里的考点,比如进程线程、TCP 三次握手、虚函数表、HashMap 底层原理,其实都会出现在面试里。所以你在刷题备考笔试时积累的知识点,是直接能迁移到面试准备的。我建议你把每次做错的题记录下来,单独建一个文档,每个错题后面加一行“为什么错”和“正确思路”,这个文档秋招全程都用得上。
另外,如果你投的是嵌入式软件开发方向,那么除了这套卷子里通用的算法和语言基础,你还要额外关注 ASPICE 软件开发流程、S32K314 这类 MCU 的开发工具链、内存映射和中断处理相关内容。这些不会出现在通用软件开发笔试里,但会在嵌入式岗的专业面试中被反复追问。方向不同,侧重点完全不同,别用一套策略打天下。
还有一个很多应届生忽视的点:项目复盘。笔试不考项目,但如果你在简历里写了“基于 Spring Boot 的商品秒杀系统”或者“基于 STM32 的智能家居控制终端”,面试官一定会深挖你的技术选型和实现细节。从笔试结束到面试通知,正常情况下有 5 到 10 天,这段时间正好用来把项目里的每一个技术点吃透。比如你用了 Redis 缓存,你就要能说出缓存穿透、缓存击穿、缓存雪崩的区别和解决方案;你用了消息队列,你就要知道它在你项目里到底解决了什么问题,不用的话会怎么样。
5. 写在最后的几点体会
这套 2022 年小米秋招笔试软件开发卷 2,整体难度在同类大厂中属于中等偏上,但题型和考点并不偏门。能感受到它们出题的核心逻辑不是“考倒你”,而是“区分你”:基础扎实、代码熟练、有工程经验的人,分数一定不会低。
我现在回头看,最大的感触是:校招笔试其实是一场“熟练度”的比拼,智力因素占比没有想象中高。那些看上去很厉害的人,无非是把常考题型练到了肌肉记忆的程度,把基础概念背到了脱口而出,然后把时间管理策略提前演练过而已。你完全可以照着这个思路去准备。
如果你正在被笔试折磨,别焦虑。把上面提到的考点清单挨个过一遍,把滑动窗口和最近公共祖先这两道编程题手写三遍以上,再把三年来的真题按时间卡着做几套,你笔试这一关基本就稳了。到最后你会发现,所谓好的结果,不过是把应该做的练习按时做完,然后顺其自然地到来。