ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

2021小米秋招算法笔试复盘:核心考点与编程题思路解析

2021小米秋招算法笔试复盘:核心考点与编程题思路解析 2021年小米秋招算法方向第一场笔试复盘每年到了秋招季算法岗笔试都是淘汰率最高的一关。2021年小米秋招算法方向第一场笔试我是在牛客网线上完成的整体感受是单选题覆盖面广、编程题难度居中偏上但区分度很清晰。这篇复盘我把当时的题型结构、核心考点、编程题思路和代码完整整理出来给准备大厂算法岗笔试的同学一个可参考的复习坐标。不管你是2022届、2023届还是更后面的学弟学妹只要目标是算法岗这套题反映出来的考察倾向基本不会过时。先说结论小米算法笔试主要考三块——数据结构与算法基础KMP、排序、树、图、常见算法设计范式贪心、分治、动态规划、搜索、C/Java语言细节和简单概率题。编程题一般是两到三道我遇到的是两道一道字符串处理一道任务调度类问题。下面按题型逐块拆解。1. 笔试整体结构与命题风格复盘1.1 题型分布与分值逻辑2021年小米秋招算法方向第一场笔试整体分为两个部分第一部分是单选题第二部分是编程题。单选题大约20道左右每道分值不高但正确率直接决定能不能进下一轮面试。编程题一般是两道少数场次有三道每题有多个测试用例输出格式严格和LeetCode的核心代码模式不一样小米笔试用的是ACM模式也就是要自己处理标准输入输出这一点很多人直接栽了。单选题的覆盖范围很广我印象比较深的有这么几类KMP算法中next数组的计算、排序算法的时间复杂度与稳定性判断、二叉树遍历的递归与迭代写法、哈希表冲突处理、贪心算法的适用场景、概率统计基础题比如抛硬币、随机变量期望、C的虚函数和内存布局。这意味着你在复习的时候不能只刷代码题基础概念题必须过一遍尤其是指针、引用、const、静态变量这些C高频考点。分值逻辑上编程题占大头。两道题如果全过基本就能稳进面试如果只过一道选择题正确率又在中等水平就可能被卡在笔试线附近。所以我的建议是选择题尽量保证正确率编程题至少完整解出一道并且通过所有样例。你不需要在笔试中做出所有的题但一定要保证做出来的题是满分状态。1.2 命题风格为什么小米喜欢这么考小米的算法笔试有一个明显特点不搞偏题怪题考察的都是经典模型但会在边界条件和输入输出上做文章。比如字符串题你不会觉得没见过但稍不注意就会在某些特殊输入上挂掉。这和大厂的筛选逻辑一致——面试官想知道的是你的基本功是否扎实、代码是否稳健而不是你背了多少冷门算法。从岗位方向上来说“算法方向”在小米内部其实包含推荐、搜索、NLP、CV等多个子方向但笔试是统一一套题所以考的都是通用算法能力。你不会被问到“音频重采样算法”或“PID算法怎么调参”这种具体领域的问题那些是后续业务面试才会涉及的内容。笔试阶段就是筛选数据结构扎实不扎实思路清不清楚代码能不能一次写对。复盘以后我强烈建议准备小米笔试的同学不要只刷难题而是把经典题刷透。比如无重复字符的最长子串、任务调度器、编辑距离、最长公共子序列、岛屿数量、二叉树层序遍历这些题反复出现在小米、美团、百度、字节的笔试题里它们是真正的“高频考点”。2. 单选题高频考点拆解KMP、排序与复杂度2.1 KMP的next数组到底怎么推KMP是笔试选择题里的常客2021年小米这场就考了next数组的计算。题目大概是对于模式串p abacaba按照next[i]定义为子串p[0..i]的最长相等真前后缀长度求对应的next数组。我在这里先说说两种常见的next定义因为很多教材和网上博客的定义都不太一样你要是搞混了选择题肯定错。第一种定义最长相等真前后缀长度next[i]表示p[0..i]这个前缀子串中最长的相等真前缀和真后缀的长度。比如pabacabai0子串a真前后缀为空next[0]0i1子串ab前缀有a后缀有b不相等next[1]0i2子串aba前缀有a、ab后缀有ba、a最长相等的是a长度1next[2]1i3子串abac前缀a、后缀c不相等next[3]0i4子串abaca前缀有a、ab、aba、abac后缀有a、ca、aca、baca最长相等的是a长度1next[4]1i5子串abacab前缀有a、ab、aba、abac、abaca后缀有b、ab、cab、acab、bacab最长相等的是ab长度2next[5]2i6子串abacaba前缀有a、ab、aba、abac、abaca、abacab后缀有a、ba、aba、caba、acaba、bacaba最长相等的是aba长度3next[6]3所以next数组是[0, 0, 1, 0, 1, 2, 3]。第二种定义失配时跳转的位置有的教材把next[i]定义为当p[i]匹配失败时模式串指针应该跳到的位置。这种定义下next值会比前一种整体右移、有的位置还要减1而且next[0]-1。如果题目里没有明确说明“next[i]定义为最长相等真前后缀长度”那你最好先看一下题目给的定义再计算。我在笔试时是先判断定义再动手避免被这种“定义差异”坑到。如果你想在考场上快速验证可以手写一个求next数组的代码心里模拟一遍vectorint getNext(string p) { int m p.size(); vectorint next(m, 0); for (int i 1, j 0; i m; i) { while (j 0 p[i] ! p[j]) j next[j - 1]; if (p[i] p[j]) j; next[i] j; } return next; }这个代码对应的是第一种定义也是力扣、牛客上最常见的写法。平时刷KMP相关题把这一种写法吃透就够用了。2.2 排序算法横向对比与笔试陷阱选择题里排序算法也是必考项。小米这场我记得考了“堆排序建堆的时间复杂度”和“快速排序最坏时间复杂度”这种题你要是只记结论不理解的容易卡壳。我直接给出笔试最容易考的对比表排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序O(n^2)O(n^2)O(1)稳定选择排序O(n^2)O(n^2)O(1)不稳定插入排序O(n^2)O(n^2)O(1)稳定希尔排序O(n^1.3) 左右O(n^2)O(1)不稳定归并排序O(n log n)O(n log n)O(n)稳定快速排序O(n log n)O(n^2)O(log n)递归栈不稳定堆排序O(n log n)O(n log n)O(1)不稳定最容易踩的坑有三个第一快排最坏情况是数组已经有序或逆序时如果每次选的基准都是最大或最小元素递归深度会退化到n时间复杂度O(n^2)。笔试选择题经常把“快排平均O(n log n)、最坏O(n^2)”和“归并排序始终是O(n log n)”放在一起混淆别选错。第二堆排序建堆的时间复杂度是O(n)不是O(n log n)。你从最后一个非叶子节点开始向下调整调整次数总和是O(n)这个结论很多人记混。第三稳定性的判断方法是看“相等的元素在排序后是否保持原有相对顺序”冒泡、插入、归并是稳定排序选择、快排、堆排不稳定。2.3 复杂度计算与主定理速查小米笔试里有一类送分题是“给你一个递归式问时间复杂度”比如T(n) 2T(n/2) O(n)是多少。这种题用主定理可以秒杀。主定理针对形如T(n) aT(n/b) f(n)的递归式比较f(n)和n^(log_b a)的增长率条件结论f(n) n^(log_b a)多项式意义下小T(n) Θ(n^(log_b a))f(n) ≈ n^(log_b a)T(n) Θ(n^(log_b a) log n)f(n) n^(log_b a)多项式意义下大且满足正则条件T(n) Θ(f(n))比如归并排序是T(n)2T(n/2)O(n)n^(log_2 2)n属于第二种情况所以T(n)O(n log n)。二分查找T(n)T(n/2)O(1)n^(log_2 1)1也是第二种情况T(n)O(log n)。需要提醒的是很多选择题不会直接问主定理而是把递归展开让你猜。这时候你脑子里要有一个预估如果每次规模减半但只处理一次一般就是O(log n)如果每次减半但处理两个子问题就是O(n log n)如果规模只减1一般是O(n^2)或O(2^n)。这些估算能力比死记结论更可靠因为笔试现场的题往往稍微变形了一下。3. 编程题真题思路还原与代码实现编程题是笔试的重头戏。小米2021秋招算法方向第一场笔试的两道编程题我根据自己的回忆和同届同学的交流整理成下面两个考点一致的题目。题目描述我做了重新表述但核心考点、数据范围和边界条件与原题保持同一水平。3.1 第一题无重复字符的最长子串题目描述给定一个字符串s请你找出其中不含有重复字符的最长子串的长度。示例输入 s abcabcbb输出3因为最长子串是abc。输入 s bbbbb输出1。输入 s pwwkew输出3最长子串是wke。这道题是滑动窗口的经典题目。笔试遇到它不要想复杂直接用双指针维护一个窗口右指针一直往右走同时用一个哈希表记录窗口内每个字符最后出现的位置。每次遇到重复字符就把左指针跳到重复字符上一次出现位置的下一个位置过程中不断更新最大长度。#include bits/stdc.h using namespace std; int lengthOfLongestSubstring(string s) { vectorint lastPos(128, -1); int left 0, ans 0; for (int right 0; right (int)s.size(); right) { char c s[right]; if (lastPos[c] left) { left lastPos[c] 1; } lastPos[c] right; ans max(ans, right - left 1); } return ans; } int main() { string s; while (getline(cin, s)) { cout lengthOfLongestSubstring(s) endl; } return 0; }两个细节值得注意一是lastPos[c] left这个判断很关键因为窗口在移动哈希表里可能有窗口外的旧位置不能用“是否等于-1”来判断是否重复而要用“是否在窗口内”二是输入可能有多行所以用getline循环读取这也是ACM模式常见的处理方式。边界情况空字符串返回0单个字符返回1全相同字符返回1。这道题时间复杂度O(n)空间复杂度O(128)实际可以认为O(1)。3.2 第二题任务调度器题目描述给定一个用字符数组tasks表示的任务列表每个字符代表一种任务类型相同任务之间必须间隔n个时间单位才能再次执行。每个单位时间可以执行一个任务或处于待命状态。请计算完成所有任务所需的最短时间。示例tasks [A,A,A,B,B,B]n 2输出8。执行顺序可以是A - B - 待命 - A - B - 待命 - A - B。这道题有两个子思路。第一个是贪心数学推导先统计每个任务的次数找到出现次数最多的任务设maxCnt为最大次数maxNum为出现次数等于maxCnt的任务种类数。最短时间至少是(maxCnt - 1) * (n 1) maxNum但结果不能小于任务总数。第二个思路是用最大堆模拟每个时间单位取出当前可执行的任务中剩余次数最多的任务执行再冷却n个时间单位后放回堆。我建议笔试时优先用数学推导法代码短、不容易错复杂度O(n)而且能处理大数据量。堆模拟法更通用但要小心处理“冷却中的任务还没到时间就放回堆”这种细节写起来容易出bug。#include bits/stdc.h using namespace std; int leastInterval(vectorchar tasks, int n) { vectorint cnt(26, 0); for (char c : tasks) cnt[c - A]; int maxCnt *max_element(cnt.begin(), cnt.end()); int maxNum 0; for (int i 0; i 26; i) { if (cnt[i] maxCnt) maxNum; } int ans (maxCnt - 1) * (n 1) maxNum; return max(ans, (int)tasks.size()); } int main() { string s; int n; while (cin s n) { vectorchar tasks(s.begin(), s.end()); cout leastInterval(tasks, n) endl; } return 0; }这道题笔试时容易在输入格式上出错。tasks和n在一行输入tasks是一个没有空格的字符串所以用cin读取字符串再转换成vector 。如果你按“读一个整数n”的格式写可能会读不到或死循环。另外测试用例里可能包含空格分隔的任务列表比如A B A这时候用getline读取整行再按空格拆分更稳妥。3.3 第三题编辑距离有些场次的笔试会加一道动态规划题我当时这场没有遇到但这里值得顺带整理因为编辑距离在算法岗笔试里的出场率实在太高了。题目描述给你两个单词word1和word2请返回将word1转换成word2所使用的最少操作数。可以对一个单词进行三种操作插入一个字符、删除一个字符、替换一个字符。状态定义是dp[i][j]表示word1前i个字符转换成word2前j个字符的最小操作数。转移方程如果word1[i-1] word2[j-1]dp[i][j] dp[i-1][j-1]否则dp[i][j] min(dp[i-1][j] 1, dp[i][j-1] 1, dp[i-1][j-1] 1)分别对应删除、插入、替换。#include bits/stdc.h using namespace std; int minDistance(string word1, string word2) { int m word1.size(), n word2.size(); vectorvectorint dp(m 1, vectorint(n 1)); for (int i 0; i m; i) dp[i][0] i; for (int j 0; j n; j) dp[0][j] j; for (int i 1; i m; i) { for (int j 1; j n; j) { if (word1[i-1] word2[j-1]) { dp[i][j] dp[i-1][j-1]; } else { dp[i][j] min({dp[i-1][j] 1, dp[i][j-1] 1, dp[i-1][j-1] 1}); } } } return dp[m][n]; } int main() { string word1, word2; while (cin word1 word2) { cout minDistance(word1, word2) endl; } return 0; }时间复杂度和空间复杂度都是O(mn)。如果笔试时内存卡得紧可以优化成一维数组但考虑到时间是主要矛盾我建议先把二维版本写对再去想优化。考场上写出AC代码比写出“更优但可能写错”的代码重要得多。4. 现场做题的节奏控制与细节经验4.1 时间分配策略小米笔试的总时长我记得是90分钟左右。选择题量不小编程题又有难度时间分配很重要。我当时的策略是选择题控制在40分钟内最多45分钟编程题留45到50分钟。如果某道选择题卡了两三分钟还没头绪先跳过等编程题写完了再回来蒙一个。选择题的“性价比”远低于编程题一道选择题可能就1到2分而一道编程题可能占一半分数。编程题的时间分配也要按“先易后难”来先扫一眼两道题选一道思路更清晰的先写确保通过所有样例后再去攻坚第二道。我见过不少同学在第一道题上死磕优化结果第二道送分题都没时间写这是非常亏的。4.2 容易被扣分的代码细节笔试扣分往往不是因为思路不对而是代码细节出问题。我整理了几个高频扣分点第一输入输出没处理好。ACM模式下必须自己读数据、自己打印结果。很多同学平时用LeetCode的Solution类习惯了笔试时忘了写main函数和输入循环直接交上去编译器报错。你在刷题时就要有意识练习标准输入输出尤其是处理多组测试用例、可能包含空行的情况。第二数组越界。滑动窗口类题目最容易在left和right的边界上出问题。建议在写循环时画一下窗口区间是左闭右闭还是左闭右开再把边界条件写清楚。第三数据类型溢出。如果题目数据范围达到10^9以上int可能不够用要用long long。任务调度器这类题虽然用int够但涉及乘法(maxCnt - 1) * (n 1)时最好提前想一想是否可能溢出。第四变量名混淆。我见过有同学把left和right写反或者把dp[i-1][j-1]写成dp[i-1][j1]这种问题在紧张时特别容易发生。写完代码后花30秒重新读一遍关键循环检查下标。4.3 ACM模式与核心代码模式的区别力扣默认是核心代码模式你只需要实现一个函数。但小米笔试是ACM模式你需要自己写#include、main函数、读取输入和输出。我建议准备阶段就切换到ACM模式刷题或者至少每周做几道牛客网的ACM模式题目。牛客网的“剑指offer”和“公司真题”板块基本都是ACM模式用来练手很合适。现场还有一个技巧在你提交前先在本地或在线编辑器里跑一遍示例输入确认输出和题目给的一致。有时候题目会同时给多个示例你把每个示例都跑一遍不要只看第一个。我就见过示例1过了但示例2不过的情况往往是边界条件没处理好。多跑几个用例再提交省得反复提交扣罚时。5. 高频算法点自查清单与复习方向笔试结束后的复盘比笔试本身更有价值。我把自己在小米这场笔试中遇到的知识点加上历年大厂算法笔试的高频考点整理成一张清单你可以对照自查考点出现频率典型题型复习建议滑动窗口极高无重复字符的最长子串、最小覆盖子串理解双指针移动逻辑记住窗口内状态维护方式哈希表极高两数之和、字母异位词分组掌握冲突处理、常用API贪心算法极高任务调度器、跳跃游戏、分发饼干培养“局部最优推全局最优”的证明意识动态规划极高编辑距离、最长公共子序列、打家劫舍30分钟能写出经典DP的转移方程KMP算法中next数组计算、字符串匹配能手推next数组两种定义不要混堆中前K个高频元素、合并K个有序链表掌握priority_queue的使用和自定义比较器二叉树中层序遍历、最近公共祖先熟记递归和迭代两套写法排序算法中复杂度比较、稳定性判断横向对比表能手写快排和归并二分查找中旋转数组找最小值、搜索插入位置注意边界开闭复习lower_bound图论低岛屿数量、课程表拓扑排序掌握DFS/BFS拓扑排序判环我自己在复习时有一个习惯每做完一类题就在清单里打一个勾并写下这道题用了什么套路。比如看到“最小”“子数组”大概率是滑动窗口或前缀和看到“最长”“两个字符串”大概率是DP看到“最短时间”可能涉及贪心或优先队列。这种“题目特征 - 算法方向”的条件反射在笔试有限时间内的价值非常大。另外如果你投的是偏推荐、搜索的算法岗笔试后的面试中还可能被问到机器学习基础比如KL散度、ELBO推导、聚类算法、BM25相关原理。但那是面试环节的事笔试阶段先把数据结构与算法的基础题过关战线拉得太长反而两头不讨好。写在最后笔试只是第一步复盘才是涨分的关键我做完小米这场笔试后的最大感受是题目本身并不偏但想要在90分钟内稳定写完、写对靠的是平时积累的“肌肉记忆”。尤其是KMP的next数组、排序算法的稳定性、滑动窗口的边界处理这些细节如果你在考场上还要想半天基本就输了。一个很实用的复盘方法每场笔试结束后不管结果如何都把自己遇到的题目按“做对了”“做错了”“题意理解了但思路没想出来”分三类整理。做错的题分析是知识点缺失还是粗心思路没想出来的题去LeetCode或牛客找同类型题目补练三到五道。我整理这份清单也是希望帮你跳过踩坑的过程直接对准高频考点发力。最后再分享一个我在实际笔试中验证过的小技巧编程题千万不要卡在输出格式上。如果你不确定是输出一个整数还是字符串、是否需要换行就按题目示例的格式来示例输出是什么形式就照着写。ACM模式判题通常忽略行末空格但不会忽略多输出或少输出所以最稳妥的做法是先打印题目给的示例看输出是否完全一致再提交。祝你能顺利通过笔试咱们面试见。
返回列表