2019年校招季某大型互联网公司下面统一叫B公司的笔试题在当年一众大厂笔试里算是一份很典型的卷子。不少同学提前刷了两个月题考完出来第一反应都是“题型不偏就是做不快”。我后来把这份卷子的考点逐题复盘了一遍发现无论选择题还是编程题落点都很集中基本功、边界意识、编码速度。这篇文章我就把这套题从题型分布到核心算法题完整拆开讲一遍适合准备校招、尤其是算法题还没形成体系的朋友参考。这套笔试一共两小时满分100分题量不算小。选择题覆盖面很广但深度其实有限只要你把操作系统、计算机网络、数据库和数据结构的核心概念吃透拿分不难。编程题虽然有难度梯度但都属于高频考题的变体不存在“看到题就懵”的情况。换句话说B公司这套题拼的不是“谁刷过更难的题”而是“谁能在有限时间内稳定输出”。1. 笔试整体构成与出题风格1.1 题型分布与时间安排这套卷子整体分为三个模块单选题、多选题和编程题具体分布如下表所示。题型题量分值覆盖范围单选题20道每题2分共40分操作系统、计算机网络、数据库、数据结构基础多选题10道每题2分共20分概念辨析、易错知识点编程题2道每题20分共40分字符串处理、动态规划、数组操作考试时长120分钟从我接触到的反馈和模拟情况来看比较合理的时间分配是选择题多选题一共控制在40到45分钟以内剩下75到80分钟全部留给编程题。选择题一旦纠结超过2分钟就应该凭第一印象先选上并标记出来等编程题写完再回头检查。因为选择题再难也就是一两分的题编程题一卡就是二十分孰轻孰重一目了然。这里有个容易忽略的细节多选题是“少选得1分错选、多选不得分”。这意味着如果你对某个选项只有五六成把握最优策略是直接放弃它。宁可少得一分也不要因为多选一个错误项丢掉整题的两分。这个规则不是题面写出来的但你在真实考试里一定会遇到。还有一点要提前适应在线笔试系统和本地IDE不同没有断点调试、没有代码补全、没有语法高亮提示视具体平台而定。很多人平时在本地写得很顺一到在线编辑器里就各种小错误连缩进都对不齐。所以备考阶段就要习惯在纯文本编辑器里写代码练到“不看提示也能写出完整函数”的程度。1.2 考点倾向分析从整张卷子的知识点分布来看出题人的思路非常清晰操作系统必考进程与线程、死锁、虚拟内存计算机网络必考子网划分、TCP连接过程、HTTP状态码数据库必考事务特性和索引数据结构和算法则是滑动窗口、动态规划、二叉树的遍历与递归。这些考点其实都在回答三个问题你的基础概念有没有真正吃透你的代码量够不够你能不能把一个复杂问题拆成若干可执行的简单步骤B公司这套题没有故意出偏题怪题这是它“典型”的原因。所谓大厂笔试题本质上是筛选工具它不需要用偏题惩罚你只需要用基础题检验你的熟练度。因此备考重点从来不是“我见过多少冷门题”而是“常见题型的标准解法我能不能在10分钟内一字不差地写出来”。2. 高频选择题考点复盘2.1 进程与线程一张表说清差异这道选择题的题干大概是这样的下列关于进程与线程的说法正确的是 A. 同一进程内的多个线程共享该进程的地址空间 B. 不同进程之间可以直接访问对方的全局变量 C. 线程是系统进行资源分配的基本单位 D. 线程切换的开销一定比进程切换的开销大正确答案是A。我先解释为什么同一进程内的线程共享代码段、数据段、堆、文件描述符等资源每个线程独享的只有栈和寄存器上下文。所以“共享地址空间”是线程最核心的特征之一这也是线程被称为“轻量级进程”的原因。B错在“直接访问”。不同进程拥有独立的虚拟地址空间现代操作系统默认会做进程隔离不能直接读写另一个进程的全局变量只能通过管道、消息队列、共享内存等IPC机制通信。C错在概念反了资源分配的基本单位是进程线程是CPU调度的基本单位。D错在“一定”这个绝对化表述通用结论是线程切换比进程切换开销小因为线程切换不需要切换地址空间和页表但实际开销还取决于操作系统实现和场景不能一概而论。对比维度进程线程资源分配基本单位是否CPU调度基本单位通常不是是地址空间独立共享所属进程切换开销大小通信方式IPC机制共享内存、全局变量备考这种概念题我建议自己动手把类似对比表整理一遍。看别人的总结十遍不如自己写一遍印象深刻。笔试中很多同学不是不知道概念而是对“边界”模糊比如把“调度单位”和“资源分配单位”搞混。考场上时间紧一旦记忆不精确很容易掉进这种选项陷阱里。2.2 子网划分掩码计算现场推演子网划分几乎是计算机网络笔试的常客。这次考的是以下题型某主机IP地址为192.168.10.129/26该主机所在子网的网络地址是 A. 192.168.10.0 B. 192.168.10.128 C. 192.168.10.192 D. 192.168.10.129先看/26的含义子网掩码前26位为1点分十进制表示为255.255.255.192。计算网络地址需要把IP地址和掩码做按位与运算。只看最后一段129的二进制是10000001192的二进制是11000000两者按位与得到10000000也就是128。所以网络地址为192.168.10.128选B。还有更快的算办法/26相对C类地址默认掩码/24多借了2位子网间隔是2的“剩余主机位数”次方也就是2^(8-2)64。从0开始数子网块是0、64、128、192。129落在[128, 192)区间内所以网络地址就是128。考试时没必要每次都在纸上算二进制把常见掩码对应的子网间隔直接背下来/25间隔128/26间隔64/27间隔32/28间隔16。能帮你节省不少时间。补充一个细节广播地址是网络地址的主机位全1也就是12863191所以该子网可用主机地址范围是192.168.10.129到192.168.10.190一共62个可用IP。2.3 数据库事务ACID的易混淆点数据库题目考的是事务特性原题大意如下事务在执行过程中系统崩溃重启后数据库将未提交事务的所有修改回滚这保证了事务的 A. 原子性 B. 一致性 C. 隔离性 D. 持久性答案选A原子性。原子性的含义是事务中的所有操作要么全部成功提交要么全部失败回滚不存在中间状态。崩溃导致回滚正是原子性要求的体现。容易混淆的是持久性。如果题干改成“事务提交后系统断电重启数据仍然不丢失”那对应的是持久性。做题时抓住关键词“未提交”对应原子性“已提交”对应持久性。一致性是指事务执行前后数据库的完整性约束不被破坏比如账户余额不能为负数、转账前后总金额不变。隔离性则指多个并发事务同时执行时彼此的操作不被无约束地干扰和锁、隔离级别相关。这类题拼的是概念辨析精度。我见过不少人背ACID背得很熟但一到具体场景就选错原因就是把四个特性的“触发场景”没有绑定起来。建议复习时对每个特性配一个标志性场景看到一个场景就能条件反射地选出来。3. 编程题一最长无重复字符子串3.1 题目描述与考点识别这道编程题的题面很精简给定一个字符串 s请你找出其中不含有重复字符的最长子串的长度。 示例1s abcabcbb输出 3最长子串为abc 示例2s bbbbb输出 1最长子串为b 示例3s pwwkew输出 3最长子串为wke看到“最长子串”“不重复字符”这两个关键词第一反应应该是两个东西哈希表和滑动窗口。这是最经典的双指针应用场景之一。B公司考这道题明显是想考察你能不能把“暴力嵌套循环”优化成“线性扫描”时间复杂度从O(n^2)降到O(n)。在动笔之前我建议先把三个示例手动走一遍特别是示例3。子串是连续的很多人写的时候把子序列的思路带进来导致输出错误。记住子串必须是原字符串中连续的一段这也是这道题能用滑动窗口的前提。3.2 暴力解法先保证正确再谈性能如果考场上第一反应不是最优解不用慌先把暴力解写出来拿分再说。暴力思想很直白枚举所有可能的子串起点和终点检查这段子串里是否有重复字符记录最大长度。def length_of_longest_substring(s: str) - int: n len(s) ans 0 for i in range(n): seen set() for j in range(i, n): if s[j] in seen: break seen.add(s[j]) ans max(ans, j - i 1) return ans这段代码逻辑完全正确实测能通过题目给出的所有示例。但问题在于最坏时间复杂度是O(n^2)当字符串长度为10^5级别时循环次数达到10^10在线评测系统一定超时。我之所以建议先把暴力解写出来是因为它有两个作用一是帮你确认自己对题意的理解是否正确二是作为后续优化的对照组。如果在优化版写完后结果对不上可以用暴力解跑几个随机用例来定位问题。3.3 滑动窗口优化从O(n^2)到O(n)优化的核心是在暴力枚举中很多重复判断是没有必要的。右指针向右移动时我们并不需要重新从起点开始检查一遍只需要维护一个“当前窗口”保证窗口内没有重复字符即可。具体做法是维护两个指针left和right初始都指向字符串开头right不断向右移动把新字符加入窗口如果发现新字符已经在窗口中出现过就让left不断右移直到窗口内不再有重复字符每轮更新结果记录窗口的最大宽度。def length_of_longest_substring(s: str) - int: seen set() left 0 ans 0 for right, ch in enumerate(s): while ch in seen: seen.remove(s[left]) left 1 seen.add(ch) ans max(ans, right - left 1) return ans这段代码的复杂度是O(n)因为每个字符最多被加入集合一次、移除一次left和right各扫描一遍字符串。空间复杂度O(min(n, 字符集大小))在只有小写字母的情况下最多也就26个字符可以认为是常数级。为什么这个算法是正确的关键在于一个不变量每次循环结束后窗口[left, right]内都不含重复字符。并且对于每个right以它为右端点且不含重复字符的最长子串就是当前的窗口。所有窗口长度取最大值就是全局答案。这个解法在笔试中已经足够满分了。你可能会看到网上有一些更花哨的优化比如用数组代替哈希表以“追求”更快的常数但在笔试环境下代码的易读性和正确性远比常数优化重要。3.4 进阶优化与边界条件如果面试官继续深挖或者你在笔试剩余时间里想再稳一手可以改为记录每个字符最近出现的下标省掉while循环。思路是把“遇到重复字符时left要跳到哪”这件事交给哈希表直接计算。def length_of_longest_substring(s: str) - int: pos {} left 0 ans 0 for right, ch in enumerate(s): if ch in pos: left max(left, pos[ch] 1) pos[ch] right ans max(ans, right - left 1) return ans这里有一个很容易踩的坑当字符ch之前出现过时left要更新为pos[ch] 1但必须与当前left取最大值。为什么举个例子s abba处理到第二个a时pos[a]是0但此时left已经因为重复的b跳到了2。如果直接让left pos[a] 1 1窗口就会错误地扩展到包含之前的字符导致结果偏大。必须写成 left max(left, pos[ch] 1)。另外考虑边界条件s为空时直接返回0s长度为1时返回1s全部是相同字符时返回1。这些用例在提交前一定要自己过一遍很多线上错误都是因为空串或单字符用例没过。如果题目要求输出最长子串本身而不是长度可以这样改在更新ans时同步记录窗口起点最终切片返回。代码改动很小感兴趣的同学可以自己写一遍。4. 编程题二买卖股票的最佳时机4.1 题目变形与状态设计第二道编程题同样来自高频题库描述如下给定一个数组 prices它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择某一天买入并选择未来的某一天卖出求能获得的最高利润。如果无法获得利润返回 0。 示例1prices [7,1,5,3,6,4]输出 5 示例2prices [7,6,4,3,1]输出 0题目明确限定“只能买卖一次”这决定了它是最简单的股票问题。考场上看到这类题第一件事是判断是“一次买卖”还是“无限次买卖”两者的状态转移写法完全不同尤其用状态机模板时很容易写混。理解这道题的关键是最低价格一定要出现在最高价格之前所以不能简单地取“最大值减最小值”那样可能变成先卖后买。比如[7,1,5]如果直接最大值5减最小值1还是对的但[5,10,1,4]直接算会得到9而实际最大利润是买1卖4得3原题示例2甚至会出现负利润所以必须按顺序扫描。4.2 状态机解法与一次遍历状态机解法是这类题的通法。定义两个状态dp[i][0]第i天结束时不持有股票的最大利润dp[i][1]第i天结束时持有股票的最大利润初始条件dp[0][0] 0dp[0][1] -prices[0]表示第一天买入后现金收益为负的股价。状态转移逻辑如下今天不持有要么昨天也不持有保持不变要么昨天持有今天卖出取两者最大值今天持有要么昨天持有保持不变要么今天买入。由于只能买卖一次买入时的现金成本就是0减去当天价格不需要考虑之前赚的钱写出状态转移就是dp[i][0] max(dp[i-1][0], dp[i-1][1] prices[i]) dp[i][1] max(dp[i-1][1], -prices[i])最终答案取dp[n-1][0]因为最后一天手里还持有股票的话利润一定不是最大的。看上面代码你可能会发现每个状态只依赖前一天所以可以压缩为两个变量def max_profit(prices) - int: if not prices: return 0 cash 0 hold -prices[0] for price in prices[1:]: cash max(cash, hold price) hold max(hold, -price) return cash这段代码有另一个等价理解方式维护“历史最低价格”每次计算“当天价格减去历史最低价”取最大值。def max_profit(prices) - int: ans 0 min_price float(inf) for price in prices: if price min_price: min_price price else: ans max(ans, price - min_price) return ans两种写法实测结果完全一致。第一种更容易扩展遇到“可以无限次买卖”时只用改动一行把hold的更新从max(hold, -price)改成max(hold, cash - price)即可第二种在笔试中写起来更快。我的建议是两种都掌握考试时看剩余时间选择。4.3 边界情况与复杂度分析这道题的边界情况看起来简单但真出错的人不少。第一个是空数组或None没有价格自然无法交易返回0第二个是只有一个价格一天之内不可能买卖同样返回0第三个是价格单调递减最大利润为0不能因为“看起来应该亏钱”就返回负数。复杂度是O(n)时间、O(1)空间在整个笔试中属于标准且稳妥的解法。写完后建议自己在脑内把[7,1,5,3,6,4]完整跑一遍确认cash的最终结果是5而不是其他值。这种题最大的敌人是“思路懂了但代码细节写歪”比如把遍历起点写成0或者把hold price写成price - hold符号一错全错。5. 实战复盘容易踩的坑与排查技巧5.1 边界条件自检清单考场上编程题做不完很多时候不是思路卡住而是被边界条件恶心到。我在辅导同学模拟笔试时总结了一张自检清单每次提交前可以快速过一遍输入为空时代码是否直接崩溃字符串为空、数组为空、链表为空都要有对应处理数组长度为1时循环会不会越界很多题把遍历起点写成0或1长度1时就出问题所有元素都相同或都极端时答案是否符合直觉比如全是重复字符、全是递减价格下标相减会不会出现负数窗口长度right - left 1left如果跳到right后面长度可能变成负数极端大数会不会溢出比如计算“峰值差”时如果价格数组数值很大注意某些语言中int范围建议每道题写完不要急着提交先把题目给的示例跑一遍再自己补一组最大规模用例和一组空值用例。这个习惯能帮你拦住80%的线上提交错误。5.2 输入输出与在线评测的细节在线笔试和本地IDE最大的不同是它没有一个可交互的调试器。很多同学在本地把代码调好了一贴到在线编辑器里就出问题原因通常是输入解析写错了。B公司这套题采用标准输入输出支持多行数据我推荐一个通用模板import sys def solve(): data sys.stdin.read().split() # data[0]通常是数组长度或字符串内容 # 按题目要求解析即可 n int(data[0]) arr list(map(int, data[1:1 n])) # 计算结果 ans ... print(ans) if __name__ __main__: solve()用sys.stdin.read().split()比逐行input()更稳妥因为你不确定测试数据有没有多余的空行。另外Python的print自带换行不需要再手动加“\n”。用Java或C的朋友注意在读入数据前不要输出任何提示信息否则会直接判格式错误。5.3 时间分配与做题顺序做题顺序是个容易被忽视的策略问题。我见过有人一上来就死磕编程题结果选择题只剩10分钟手忙脚乱连蒙带猜最后总分反而不如先把选择做稳的同学。我建议拿到卷子先花2到3分钟把所有题目扫一遍把编程题按难度排序。先做那道思路最明确的建立“已经AC了一题”的稳定感然后回头做选择题最后再攻剩下那道编程题。如果编程题卡了20分钟还没有完整思路果断跳过留到最后再写。多选题的规则前面说过少选给1分、错选不给分。把握不大的选项直接不选。不要为了“赌一把”多选一个大概率丢的就是整题2分。真实考试中这几分可能就决定了你能不能进入下一轮。6. 备考建议与扩展练习方向6.1 从这套题看校招算法题的底层逻辑B公司这套卷子整体难度不高但区分度很好原因在于它选拔的不是“谁背的题多”而是“谁的基本功稳”。选择题考概念精度编程题考编码速度与边界意识两者结合基本能判断出一个人的代码水平。所以我一直跟准备校招的朋友说刷题不必追求“难题偏题集”把高频题型的标准解法练到肌肉记忆才是性价比最高的备考方式。什么叫肌肉记忆就是拿到滑动窗口题不需要想就能写出左右指针更新逻辑拿到一次买卖股票题不需要犹豫就能写出状态转移。做到这一点才有余力去处理考场上的突发状况。6.2 推荐练习路径建议按三个阶段来推进第一阶段夯实数据结构基础数组、链表、栈、队列、哈希表、二叉树这些结构的手写实现要能一次性写对。第二阶段攻克高频算法模板滑动窗口、双指针、二分查找、回溯、DFS/BFS、基础动态规划每个模板至少刷10道经典题做到“看到题型直接套框架”。第三阶段做限时模拟每周安排2到3次完整的线上模拟笔试用真实平台、真实时间限制来练习不要给自己“再想想”的余地。每道题建议刷三遍第一遍自己写能写多少写多少第二遍对照标准解法搞清楚差距在哪第三遍隔三天重新默写确保不靠提示也能写出完整的AC代码。这个方法听起来蠢但实测下来比盲目刷量有效得多。6.3 心态与临场细节最后聊几个临场细节。在线编辑器没有代码补全变量命名稍微规范一点不然写到最后自己都容易混乱。递归深度过大的时候Python可能报栈溢出遇到树相关题目建议先把递归改成栈迭代或者提前设置递归限制。写完代码一定要手动点“提交”别一直停在“运行”页面这个错误看起来离谱但每年都有人犯。还有一点不要因为某道题没写出来就心态崩溃。校招笔试筛的是相对排名后面还有面试可以拉回来。稳住心态把能拿的分全拿到结果往往比你预期的好。我个人在实际辅导中的体会是每年准备校招的同学里最终考出好成绩的往往不是刷题数量最多的而是把高频题型模板整理得最清晰的人。他们会在笔试前把滑动窗口、状态机DP这些套路在白纸上默写几遍确保不依赖编辑器提示也能一次写对。这份复盘里的两套代码也建议你照着这个思路练一直练到形成条件反射。如果这套笔试题的题型你都吃透了再遇到同款题你就比大部分竞争者多一分底气和从容。