
这几年看春招和秋招的笔试复盘发现一个反直觉的现象很多人刷了几百道题真题模拟也做了一堆结果在线笔试照样挂反而是平时刷题量不大的人笔试通过率高得惊人。问题不出在题目难度而是绝大多数人把笔试编程题当成了“算法竞赛”一上来就暴力、秀技巧、图最快却忘了大厂笔试本质上是一场限时工程能力测试。今天这篇文章我不打算带大家刷完某个具体题库而是借一家以电商业务起家的头部互联网公司以下统一叫“某大厂”的笔试风格聊聊笔试编程题背后那套筛选逻辑以及真正有效的备考路径。无论你正在准备校招还是跳槽只要目标里有这种量级的大厂这篇文章应该能帮你避开我见过最多的那些坑。1. 先说清楚某大厂笔试编程题到底在筛什么1.1 从“会做题”到“会工程化表达”的差距很多人有一个错觉笔试嘛只要AC就好。但真正参与过阅卷或面试复盘就会知道在线笔试的判定远不止“输出对不对”。自动化用例会跑掉大部分错误答案但遇到性能边界、内存边界、极端输入能活下来的代码通常不是最聪明的而是最“稳”的。举个例子一道求子集最优解的题有人用递归回溯有人用动态规划两者都能通过小规模样例。可当n从20变成1000时回溯直接超时这就是典型的“用例全过但复杂度不行”。反过来有人写了最优复杂度但忘记处理输入为空的情况或者全局变量初始化位置写错在笔试系统的并发测试里直接串值一样拿不到分。所以公司要筛的不是“会不会某个算法”而是“遇到一个之前没见过的业务问题你能不能快速抽象成数学模型写出一个可读、可靠、复杂度合理的程序”。1.2 笔试考察的能力清单与常见误区与其猜题目不如先按照笔试官方的“能力雷达图”反推。我把这些年在笔试中看到的失败案例归纳成五类基本覆盖所有失分点。能力维度考察方式常见失败原因问题抽象用自然语言描述业务场景让候选人转成输入输出模型没看清数据范围就动手写凭感觉设计数据结构算法与复杂度权衡大样例验证时间复杂度隐藏用例卡常数只会套模板不理解模板的适用前提边界处理空列表、单元素、极大极小值、溢出不写防御式代码默认所有输入都合法代码工程能力阅卷人肉眼查看变量命名、函数拆分、注释一个Main函数堆到底命名全是a、b、tmp调试能力遇到答案错误时是否能在有限时间内定位没有打印日志的习惯一错就换思路重写这五类能力里最容易被忽视的是“问题抽象”。很多候选人看到长题干就慌习惯性在草稿上画输入输出格式却没花时间把“业务约束”翻译成算法约束。比如题干里出现“订单优先级”“库存上限”“时间窗口”这些词背后可能就是排序、背包、区间调度。读题前30秒的正确动作不是准备代码模板而是圈出所有数字约束n的取值范围、每条数据的取值范围、输出是否要排序——这些才是决定算法选型的核心。2. 高频题型地图每种题型的“死穴”在哪里2.1 数组、字符串与双指针模板背后是“单调性”笔试里数组和字符串永远占大头但真正让人挂掉的不是不会写双指针而是不知道什么时候能用双指针。滑动窗口的逻辑很简单右指针往前探左指针跟在后面收缩窗口维护一个状态。可很多人背了模板却忽略了“状态必须单调”这个前提。比如找满足某个条件的最短子数组如果条件关于窗口长度不是单调的滑动窗口就会失效。我建议碰到涉及“连续子数组”的题先画一个例子把右端点固定住看左端点右移时窗口状态是变好还是变坏。只有状态是单向变化的双指针才成立。否则老老实实考虑前缀和、二分或者哈希表。这个思考过程笔试时只要花两分钟就能省下后面大量重写时间。2.2 动态规划别急着想状态转移先想暴力动态规划是大厂笔试的分水岭也是最容易被“套路”带沟里的题型。很多人喜欢从记忆化递推公式开始背但一遇到变形题就卡住。我的习惯是反着来先写暴力递归比如dfs(index, remaining)然后观察递归参数的数量和取值范围。有几个可变参数状态就是几维参数取值范围相乘就是状态总数。状态总数不大就用记忆化状态总数太大就需要考虑压缩、贪心或数据增益。拿经典的0-1背包举例暴力递归就是dfs(i, cap)从第 i 个物品开始当前背包容量 cap返回后续能获得的最大价值。把这个递归写成两层循环的DPdp[i][cap]就是在填一张二维表。很多候选人卡在“为什么倒序更新一维数组”其实只要理解为“每个物品只能用一次”倒序保证了不会把当前物品重复放进去。这个道理通了比背一百道背包题都有用。2.3 图论与搜索真正拉开差距的地方图论题在某大厂笔试里占比不如数组和DP但一旦出现往往是压轴分。这里的核心技术点不是最短路径模板而是“状态设计”。常见题型有多源BFS、拓扑排序、状态压缩BFS、带权最短路。死穴也出在这里很多人算不清状态总数导致内存超限或者时间复杂度假得离谱。举个例子一个二维迷宫最短步数BFS没问题。但如果额外增加“可以消除一个障碍”状态就从(x,y)变成(x,y,used)used只有0和1两种总状态数乘以2BFS依然可行。可有人没拆这个维度非要在队列里存“最多还能消几次”每次出队再枚举后续所有可能结果状态爆炸。另一个容易被忽略的是图论里模棱两可的部分。笔试题目一般不会直接说“这就是Dijkstra”而是用“城市”“航线”“站台”这类包装。你要能识别出节点、边、权重的本质再套用合适的算法。如果题意是“经过中转的次数最少”那就是无权图的BFS不是Dijkstra如果“每条路有长度且要求总路程最短”那才是Dijkstra。把问题翻译成图的建模能力比背算法库更有用。3. 用一道“订单分配”模拟题完整还原笔试现场3.1 题目描述与输入输出约定为了把上面的思路落到细节我构造了一道非常典型的模拟题主题贴合电商业务但考的是0-1背包。这种题在大厂笔试里很常见特点是业务背景复杂但模型简单。我把题目描述和输入输出格式都写清楚你可以拿它做一次完整的笔试演练。模拟项目X某电商平台仓库有库存总量S只存放一种热门商品。平台收到了n个订单第i个订单需要w[i]件商品完成后可以获得p[i]元利润。每个订单只能选择接受或拒绝且不能分批发货。平台希望在不超过库存S的前提下选出若干订单使得总利润最大。注意商品件数和利润都是正整数。输入格式第一行包含两个整数n和S分别表示订单数和库存总量。第二行包含n个整数依次是每个订单的需求量w[1]到w[n]。第三行包含n个整数依次是每个订单的利润p[1]到p[n]。样例输入4 5 2 1 3 2 5 3 4 2对应最优解是选择订单1、2、4需求量为2125利润为53210。3.2 读题后的三个动作确认数据范围、建模、考虑复杂度如果这是一场真实的在线笔试看到这道题时前两分钟应该做三件事。第一圈数据范围。题目虽然没有明确写n和S的最大值但这类题通常n可能在100到1000之间S可能在10000以内。如果想要稳过先假设n≤500S≤10000那么O(n*S)就有可能在时间限制内。如果n到了10的5次方S也很大那就不能简单用背包而要考虑搜索剪枝或贪心近似。第二建模。每个订单只有接受或拒绝利润和需求量一一对应这显然是一个0-1背包的“物品选与不选”模型。库存总量S是背包容量w[i]是物品重量p[i]是物品价值。第三定方案。如果选择二维DP空间复杂度O(nS)当n500、S10000时就是500万个int约20MB在线笔试内存一般给256MB或更多可以接受。但为了更稳可以用滚动数组压成一维空间直接降到O(S)。这就是我前面说的先把暴力或基础解法建立起来再通过观察转移特点做优化比一上来就追求所谓最优解明显更安全。3.3 从暴力递归到一维动态规划的推导很多人知道0-1背包的公式但不知道它是怎么来的。我建议在脑子里快速过一遍暴力递归// 从第i个订单开始剩余容量cap能获得的最大利润 int dfs(int i, int cap) { if (i n) return 0; int res dfs(i 1, cap); // 不要第i个 if (cap w[i]) { res Math.max(res, dfs(i 1, cap - w[i]) p[i]); // 要第i个 } return res; }这个递归的解就是正确的但慢在重复计算。如果给这个递归加上记忆化它就已经是二维DP的雏形。接下来转成迭代用dp[i][cap]表示前i个订单在容量cap下的最大利润。转移方程是dp[i][cap] max(dp[i-1][cap], dp[i-1][cap-w[i]] p[i])再用滚动数组压缩成一行从后往前更新避免覆盖尚未读取的上一个状态。完整代码如下import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { BufferedReader br new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st new StringTokenizer(br.readLine()); int n Integer.parseInt(st.nextToken()); int S Integer.parseInt(st.nextToken()); int[] w new int[n 1]; int[] p new int[n 1]; st new StringTokenizer(br.readLine()); for (int i 1; i n; i) { w[i] Integer.parseInt(st.nextToken()); } st new StringTokenizer(br.readLine()); for (int i 1; i n; i) { p[i] Integer.parseInt(st.nextToken()); } int[] dp new int[S 1]; for (int i 1; i n; i) { for (int cap S; cap w[i]; cap--) { dp[cap] Math.max(dp[cap], dp[cap - w[i]] p[i]); } } System.out.println(dp[S]); } }这段代码可以直接跑通样例。注意输入用了BufferedReader和StringTokenizer不是为了炫技而是因为很多在线笔试的输入规模很大Scanner在循环里逐行读取容易超时。笔试是分秒必争的地方IO这里的差距经常被忽视。3.4 复杂度、边界样例与易错点复盘这段代码的时间复杂度是O(n*S)空间复杂度O(S)已经是最优标准。但真正笔试时只写出主流程还不够边界用例往往决定能不能拿到这题的满分。我建议在本地额外测三组数据n1只有一个订单。如果w[1]S就输出p[1]否则输出0。代码里两层循环天然处理了这个情况。w[i]大于S的订单。代码中内层循环从S递减到w[i]当w[i]S时根本不会进入循环所以会自动跳过这个订单。所有订单需求量总和小于S。此时dp数组会被完整更新最终答案是全部订单利润之和这也不会有问题。还有一个很多人踩的坑如果题目要求输出最大利润但又问“如果有多组最优解输出字典序最小的订单编号”那就不是上面这段代码能直接搞定的。需要在DP状态转移时当“不选”和“选”产生相同利润时优先记录“选”然后从后向前回溯订单列表。这个扩展经常被用来区分AC和精通建议在备考时单独练一下。好在这道模拟题的题干没有这个要求我们这里用最简洁版本就够了。4. 在线笔试环境里那些“看不见”的失分点4.1 输入输出的两种模式与代码模板很多人把刷题环境当IDE忽略了在线笔试的IO规则。某大厂笔试系统一般分两种模式一种是“核心代码模式”你只需要实现一个类的方法入参出参已经定义好另一种是“ACM模式”需要自己解析标准输入和标准输出。大部分校招笔试用的是ACM模式这是失分重灾区。我的做法是准备一套自己的IO模板。输入统一用BufferedReader读行再用StringTokenizer按空格拆分。输出统一放到StringBuilder里最后一次性print减少单条输出带来的系统调用。这套模板能应对绝大多数需要读多行、输出一行的场景。还有一类细节某些题目要求循环处理多个测试用例直到读入EOF。这时候不能假设一定读得到第一行。代码里应使用while (br.ready())或者while ((line br.readLine()) ! null)来判断。遇到没有更多输入时程序要能正常退出而不是抛空指针。4.2 超时与内存限制的定位技巧笔试现场最容易出现的情况是“本地跑得好好的一提交就超时”。原因无外乎三种复杂度太高、IO太慢、死循环。如果代码本地测几个小用例都正常一到大数据就超时优先怀疑复杂度如果小数据也偶尔卡死优先怀疑死循环。我见过不少候选人写了while(true)配合break条件写错本地怎么测都碰不到那条分支一到边界数据就卡死。建议所有循环都标注清楚“什么时候退出”在Debug时多打印一下计数器。内存超限的根子一般也不是“内存不够”而是状态数组开太大。比如n1000却开了一个n*n的二维数组每个格子存long内存直接爆掉。在写DP之前先算一下状态容量1000*1000*8字节8MB看起来不大但如果状态本身是结构体可能就变几十MB。一个实用原则是能用一维滚动数组不用二维能用int不用long能二分不枚举所有状态。当然前提是不溢出。4.3 代码规范性变量命名、注释与防御式编程有人觉得笔试代码只要过了用例命名无所谓。但很多大厂笔试之后面试官会重新调出你的原始代码跟面试时的讨论结合起来看。一个类名全是a、b、tmp的代码给面试官的印象就是“这个候选人的工程习惯还很初级”。反过来说变量命名能表达业务含义注释能写清楚转移方程代码结构自然分成“读入”、“计算”、“输出”三部这样即使有个别用例没过面试官也更容易帮你找到可以补救的点。防御式编程也很重要。笔试输入一般不会非法但偶尔有恶心的隐藏用例比如数组为空、数字前有多个空格、输入末尾有换行。用StringTokenizer能自动忽略多余空格但如果需要读一行自定义字符串最好先trim()一下。再比如求a * b可能溢出即使笔试不卡边际也尽量用long存储中间结果。这些不是考题重点但却是区分“能用”和“好用”的关键。5. 90天备考规划刷题数量不重要复盘习惯才重要5.1 分阶段计划前期专项、中期模拟、后期查漏如果你离笔试还有三个月我不建议从第一天就开始刷“每日一题”那样到后面很容易变成感动自己的无用功。更靠谱的方式是分三个阶段。第1到第4周做专项突破。按数据结构划分数组、链表、栈/队列、哈希表、树、图、动态规划、贪心。每天只看一类题每类至少搞懂20道题吃透“为什么用这个数据结构”而不是“这题AC了”。比如链表题就死磕“双指针、递归、头节点哨兵”这三板斧动态规划就死磕“设计状态、写转移、初始化、滚动优化”这条路。第5到第8周开始全真模拟。每周至少3次完整的在线笔试用真实的限时环境一次2小时做一套完整试卷。重点不是分数而是建立“时间分配”的手感。我的建议是前5分钟扫完全部题目给每道题预设一个时间上限比如简单题20分钟、中等题35分钟、难题留30分钟但允许放弃。一旦超过预设时间立即跳过做下一题不要恋战。第9到第12周回归错题和查漏。把前三周做错的题重新拿出来限时重写。你会发现很多题当时看题解秒懂一周后再写还是卡在同一处。这时候才能把短板彻底补上。如果时间紧张至少要保证每周做一次的限时模考同时把之前的错题本翻一遍。5.2 如何做“一题三遍”的深度复盘很多优秀备考生都认同一个做法同一道题至少做三遍。第一遍在限定时间内独立完成哪怕只能用暴力解也要写出可运行的代码。第二遍在AC之后立刻尝试优化复杂度或者去看别人的解法理解更优的状态设计。第三遍是在一周之后合上题解重新限时重写一次。第三遍如果能写出来而且能说明为什么之前会卡住这道题才算真正内化。复盘时我建议给每个错题记四件事题目抽象后的模型是什么、我最初为什么卡住、最优解的核心状态或单调性在哪里、如果重新来一次哪个环节可以先做判断。把这些记下来远比“今天刷了30道题”更有价值。笔试考的从来不是“见过的题多不多”而是“遇到没见过的题时能不能快速构建出解法”。5.3 笔试前一天和考试中的时间分配建议笔试前一天不要做新题更不要熬夜。把常用的IO模板背一遍把所有算法模板的核心判断条件过一遍比如二分模板的边界、BFS的visited初始化、背包的循环顺序。然后早点休息。真正决定成绩的是第二天考场上的状态。考试开始后第一件事是把所有题目快速扫一遍在草稿纸上标出容易题、中等题、难题。先做容易题保证拿分再做中等题留出足够时间最后剩时间再碰难题。如果遇到一道题5分钟没有思路果断换下一题别让一道题毁掉整场节奏。笔试不是展示执着的地方是展示产出效率的地方。还有一个小技巧输出答案前先重新读一遍样例格式尤其是“每个数字后面有没有空格”这类细节。很多人思路全对输出多了一个空格或换行被判WA这就太冤了。写完代码后用题目给的样例跑一遍再用一个自己构造的边界样例跑一遍这两个动作能避免至少30%的低级错误。6. 最后聊点个人体会看了这么多年的笔试记录我最大的感受是能通过某大厂笔试的往往不是知识点最渊博的人而是对“限时、资源、边界、输出”这些非算法约束非常敏感的人。算法思维是骨架工程习惯是血肉考场管理是神经。三者缺一都会在最不起眼的地方摔跟头。如果你最近正在准备这类考试我特别建议从今天开始做一件事每天把一道做过题的“解题路径”讲给自己听包括为什么选这个算法、边界在哪里、复杂度是多少。能讲清楚比多做十道题更有用。至于刷题数量真到笔试那天你会发现心态稳定、输入输出熟练、边界用例想得周全才是让你发挥出真实水平的关键。