第一次拿到快手2019年秋季校招的工程A卷,我的第一反应是:这份卷子出得挺“狠”。题目难度倒不算变态,但覆盖面非常广,数据结构、算法、计算机网络、操作系统、语言基础甚至工程思维全都揉在了一张卷子里。它不是那种靠背几道面经就能应付的卷子,它考的是你过去几年写代码时,到底有没有把每个基础概念背后的“为什么”想明白。
这里先说明一下:原卷属于公司内部资料,网上流传的大多是候选人回忆版本,所以我下面提到的题面,是按同一考察范围还原的典型题,不代表官方原文。但考察逻辑是真实存在的,这也是这篇复盘对你真正有价值的地方。如果你正在准备大厂校招,或者打算把快手当作目标公司之一,这套试卷的题型结构、考察倾向和答题节奏,很值得拆开来看。
1. 工程A卷到底在替快手筛选什么样的人
1.1 “工程”二字背后是岗位分层
快手校招的技术类岗位,每年都会按岗位方向拆分试卷。算法岗一套、工程岗一套、数据岗一套,题目虽然可能有重合,但侧重点完全不同。工程A卷面向的主要是后端开发、前端开发、客户端开发、测试开发这类“要真正把系统做出来”的岗位,所以它对代码实现能力的考察权重,明显高于对模型推导能力的考察。
也正因为面向的是工程岗,这份卷子特别看重两件事:第一,基础知识是否成体系;第二,能不能把知识转化成代码。你可以在选择题里看到对网络、操作系统、语言底层机制的考察,在编程题里看到需要在限定时间和内存内完成的算法实现。两者加起来,就是在模拟一个工程师“拿到需求—抽象问题—落地实现”的完整过程。
A卷这个编号本身也有信息量。多套试卷并行,说明岗位方向或难度分组有差异,A卷通常对应人数最多、最主流的工程方向。换句话说,这套卷子的成绩直接决定了你能不能进入后续的面试环节,它的重要性值得你花完整的时间去研究,而不是当成普通练习卷随手刷掉。
1.2 快手业务对工程师提出了哪些具体要求
2019年前后的快手,正处于日活快速增长的阶段,短视频上传、转码、分发、推荐、评论、私信、直播这些核心链路,全部需要大量后端和客户端工程师。这种业务背景决定了它招人时看重的东西和你刷LeetCode时看重的,不完全一样。
我梳理了几个比较有代表性的技术场景:
- 高并发写入:用户上传视频、发表评论、点赞、关注,都是高频率操作,后端需要处理峰值流量。
- 大数据量存储与检索:海量视频的元信息、用户关系、播放记录,需要合理的存储方案。
- 推荐链路优化:从内容池筛选候选集,再做排序、去重、多样性控制。
- 音视频处理:转码、缩略图生成、播放器适配,涉及大量异步任务和队列。
这些业务特点决定了笔试不会只考纯理论。比如一道“在大量数据中统计TopK热词”的编程题,背后对应的就是热榜、热门话题、审核队列这些真实业务。一道“设计一个计数器接口”的场景题,对应的就是点赞数、播放量在分布式环境下怎么做到不丢数据、不超卖。理解了这层关系,你再看试卷里的每道题,就不会觉得它们是孤立的面试题,而是一个个真实工程问题的抽象版本。
2. 高频考点拆解:四类题型的考察逻辑
2.1 数据结构与算法:得分主力,也是区分度所在
工程A卷的编程题基本都落在数据结构和算法范围内。从历届考生回忆和同类笔试的题型分布来看,有几个方向几乎必考。
第一是线性表的灵活使用。数组、链表、栈、队列是基础,但笔试很少直接问“栈是什么”,而是给你一个场景,让你发现需要用栈或者单调队列去优化。比如求滑动窗口最大值、判断括号合法性、如何用两个栈实现队列,考察的是你对数据结构特性的理解,而不是背诵。
第二是哈希表的建模能力。大多数需要“快速查找”“去重”“计数”的题目,第一反应都应该是哈希表。哈希表题目的难点不在哈希表本身,而在于你能不能想到用“键值关系”表达题目中的业务逻辑。同一个数组里找两数之和、统计字符串出现次数、判断是否存在重复元素,本质上都是在考建模能力。
第三是排序与TopK。快排、归并、堆排序是常客,其中堆排序最常被用来解决“取前K个最大或最小元素”的问题。这类题朴素解法人人都会,但时间复杂度的优化才是真正的拉分点。笔试现场能写出O(n log k)而不是O(n log n),往往意味着你已经知道“只需要保留K个元素,不需要全量排序”这个关键点。
第四是树与图的遍历。二叉树的前中后序遍历、层序遍历、最近公共祖先,图的DFS和BFS,都是笔试选择题和编程题的高频来源。快手尤其喜欢考树,因为树结构在推荐、组织架构、评论回复这些业务里到处可见。
第五是动态规划。背包、子序列、编辑距离、股票买卖这类经典DP题基本是校招笔试标配。DP题的难点在于状态定义和转移方程,对大多数同学来说,需要靠刷题量积累“题感”。但需要注意的是,DP题在工程A卷里通常不会出到竞赛难度,中等偏下的题目比例较高,重点还是看你基础牢不牢。
把这些串起来看,算法题层面,快手考察的更像是“常用算法能不能在压力下快速写出”的能力,而不是对冷门算法或竞赛技巧的突击记忆。
2.2 计算机网络与操作系统:后端候选人的分水岭
工程A卷的选择题里,计算机网络和操作系统占据的权重非常高。尤其是后端方向的候选人,这两块做不好,编程题再强也很难进面试。
网络部分的高频考点非常集中:TCP三次握手、四次挥手,以及TIME_WAIT存在的原因;TCP拥塞控制的慢启动、拥塞避免、快重传、快恢复;UDP与TCP的适用场景对比;HTTP协议的无状态特性、Cookie与Session的区别、HTTPS的握手流程;以及从输入一个网址到页面展示,中间经历了哪些过程。
操作系统部分的高频考点包括:进程与线程的本质区别,协程的出现是为了解决什么问题;进程间通信方式,包括管道、消息队列、共享内存、信号量、Socket;虚拟内存、分页、分段、页面置换算法;死锁产生的四个必要条件,以及如何避免;常见的IO模型,包括阻塞IO、非阻塞IO、IO多路复用、异步IO。
这些知识点看着多,但考察方式通常都围绕“某个机制为什么要存在”。举例来说,TIME_WAIT为什么是2MSL?因为要保证最后一个ACK到达对端,同时让旧连接的报文在网络中消失。如果只是背了状态名,不理解背后的原因,遇到变体题就会懵。
我把选择题里最容易出现的基础概念整理成了一张对照表,复习时可以对着查缺补漏:
| 考察方向 | 常考知识点 | 常见出题方式 |
|---|---|---|
| 网络 | TCP三次握手/四次挥手 | 给出状态序列,判断哪个环节出错 |
| 网络 | HTTP/HTTPS | 结合Cookie、Session考状态管理 |
| 操作系统 | 进程与线程 | 给出资源开销/共享情况,判断题干描述 |
| 操作系统 | 死锁条件 | 给出场景,判断是否会发生死锁 |
| 语言 | 值传递与引用传递 | 给一段代码,判断输出结果 |
| 语言 | 内存管理 | 结合智能指针/GC判断内存行为 |
| 数据结构 | 哈希冲突 | 给出冲突解决策略,比较查找效率 |
| 数据结构 | 二叉树遍历 | 给出前序中序,求后序或层序 |
2.3 语言基础与代码规范:选择题里的“软刀子”
工程A卷还有一个容易被忽视的部分:语言基础题。这类题不会单列一大块,但会分散在选择题和编程题里。快手后端主要使用C++和Java,也有部分Go,所以语言题基本围绕这几门语言展开。
C++方向常考:指针与引用的区别、const的各种用法、静态成员与实例成员、虚函数与多态、内存泄漏与智能指针、深浅拷贝、构造函数与析构函数执行顺序。
Java方向常考:JVM内存区域、GC机制、HashMap在不同JDK版本下的实现差异、线程池参数含义、synchronized与volatile的区别、ArrayList与LinkedList的使用场景。
Go方向如果考到,通常围绕goroutine调度、channel的使用、slice和map的底层结构、defer的执行顺序。
这些语言题的本质,是在测“你是否真的用这门语言写过生产级代码”。举个最常见的例子:一道关于HashMap的题,如果你只是背过“HashMap是数组加链表”,却说不出JDK1.8之后为什么引入红黑树、扩容阈值为什么是0.75,那这道题就很容易翻车。这类细节不是靠考试前突击能补上的,而是靠平时写代码时多看源码、多问为什么。
2.4 开放场景题:从业务抽象出的系统设计前奏
有些同学会忽略笔试里的开放题,觉得没标准答案就随便写写。实际上,快手工程A卷的开放题往往是整套卷子里最能拉开差距的部分,因为它考察的是“把模糊需求转化为明确方案”的能力。
这类题常见的形态有:设计一个短链服务,需要支持大量生成和跳转;实现一个分布式环境下的计数接口,要求不丢数据;设计一个消息推送系统,考虑离线消息的处理;给出一个线上接口变慢的问题,分析可能原因并给出排查路径。
回答这类题,有一个很容易上手的框架:
- 先明确需求边界,包括数据量级、并发量、一致性要求。
- 再给整体架构,说清楚每个模块的职责。
- 然后深入到关键细节,比如存储选型、缓存设计、消息队列的引入。
- 最后指出可能的瓶颈和容错方案。
框架的价值在于,它让你的思考过程对阅卷人可见。哪怕最终方案不是最优,只要每一步推导有理有据,就能拿到不错的分数。最怕的是只写结论不写过程,那样阅卷人无法判断你是真的理解,还是背过一个模板。
3. 典型题目复盘:三道题带你走一遍完整解题链路
下面我从工程A卷的考察范围内挑三道有代表性的题目,按“读题—思考—实现—复盘”的顺序完整走一遍。
3.1 滑动窗口最大值:先想清楚单调性,再动手写单调队列
题目描述:给定一个整数数组nums和一个窗口大小k,窗口从数组最左端滑动到最右端,每次只能向右移动一位,求每个窗口内的最大值。
这道题如果第一次见,最容易想到的是暴力解法:每次窗口移动后都遍历窗口内k个数找最大值,时间复杂度O(n*k)。这个复杂度在数据量大时基本过不了,需要用单调队列优化到O(n)。
优化的核心思路是:维护一个双端队列,队列中存储数组下标,同时保证这些下标对应的值是递减的。每次窗口滑动时:
- 从队尾开始,把所有值小于等于当前元素的下标弹出,因为它们在当前窗口及之后的窗口里都不可能是最大值。
- 将当前元素下标入队。
- 从队头检查,如果队头下标已经不在当前窗口范围内,则弹出。
- 队头对应元素就是当前窗口的最大值。
代码实现如下:
vector<int> maxSlidingWindow(vector<int>& nums, int k) { vector<int> ans; deque<int> q; for (int i = 0; i < nums.size(); i++) { while (!q.empty() && nums[q.back()] <= nums[i]) { q.pop_back(); } q.push_back(i); if (q.front() <= i - k) { q.pop_front(); } if (i >= k - 1) { ans.push_back(nums[q.front()]); } } return ans; }这道题能拿分的要点有三个:第一,能够解释清楚为什么用单调队列而不是堆或线段树;第二,代码里对下标和值的边界处理要清晰;第三,复杂度分析时能说清楚“每个元素最多入队和出队一次,所以总复杂度是O(n)”。
很多人第一次写单调队列,容易在处理“出队下标越界”这个环节写错顺序。正确做法是先清理队尾、再入队、再清理队头失效下标、最后取值。顺序反了,可能取到刚入队但已经不属于当前窗口的元素。
3.2 TopK高频元素:面试官想听的,不只是一套堆排序
题目描述:给定一个非空的整数数组,返回其中出现频率前k高的元素。
这道题几乎是大厂笔试的常青树。思路分两步:先用哈希表统计每个元素的出现频率,再在频率集合里找出前k个高频元素。
第二步的方案选择很能体现功底。最朴素的做法是把所有元素按频率排序,取前k个,时间复杂度O(n log n)。更优的做法是维护一个大小为k的小顶堆,堆顶永远是堆中频率最小的元素,遍历完所有元素后,堆里剩下的就是频率最高的k个元素,时间复杂度O(n log k)。
代码实现:
vector<int> topKFrequent(vector<int>& nums, int k) { unordered_map<int, int> freq; for (int num : nums) { freq[num]++; } auto cmp = [](const pair<int, int>& a, const pair<int, int>& b) { return a.second > b.second; }; priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp); for (auto& p : freq) { pq.push(p); if (pq.size() > k) { pq.pop(); } } vector<int> ans; while (!pq.empty()) { ans.push_back(pq.top().first); pq.pop(); } return ans; }这道题的加分点在于,如果你能主动提到更极端的方案,比如在数据量极大、无法全部装载进内存时,可以用分治或者依赖哈希分布做并行统计,就能让阅卷人看到你有工程延伸能力。这两种方案笔试不一定会要求写完整代码,但能在方案讨论中体现出来,印象分会明显不一样。
笔试时间紧张时,我用得比较顺手的顺序是:先确认能否用排序解决,再判断是否需要用堆优化,最后才考虑更复杂的方案。步骤不在多,在于每一步都能自圆其说。
3.3 实时热榜场景题:如何把模糊需求变成工程方案
场景描述:短视频App需要一个“实时热榜”功能,展示当前热度最高的N个话题,热度每分钟更新一次。请给出设计思路和关键实现。
这类场景题没有唯一正确答案,但阅卷人心里有一套“合理方案的最低标准”。我的建议是按下述思路展开。
第一步,明确需求边界。先问清楚几个关键参数:话题总量级假设是百万级,参与热度计算的互动行为(点赞、评论、分享、播放)每分钟上千万次,榜单展示前100名,允许分钟级延迟。
第二步,设计核心数据结构。热度计算可以简化为一个加权分值,比如score = 播放量 * a + 点赞量 * b + 评论量 * c + 分享量 * d,权重系数根据业务目标调整。每分钟从消息队列中消费行为流,在内存里累加各话题的分值。
第三步,选择TopK计算方案。百万级话题量级下,用一个固定大小为100的小顶堆即可完成Top100的筛选。如果话题量到亿级,还可以用分桶的思路:把分数区间划分成多个桶,优先从高分段桶里取数据。这个方案在笔试里提到,是很加分的工程思维。
第四步,给出落地链路。行为数据通过消息队列进入实时计算层,计算层维护内存计数器和榜单,定期把榜单结果写入缓存,前端从缓存读取展示。针对热点话题突然暴涨的异常情况,要能快速扩容计算节点。
场景题的高分回答,不在于方案多炫酷,而在于你展示出“先分析再设计再落地”的完整思路。哪怕技术选型没那么新颖,只要每一步推导合理,就能拿到不错的分数。
4. 限时答题的实战策略:正确率、时间与心态的平衡
4.1 时间分配:不同题型的黄金占比
快手的工程A卷整体时间一般在90分钟到120分钟之间,具体时长以当年通知为准。但题量和类型大概可以预估:10到20道选择题,2到3道编程题,可能还有1到2道简答或场景题。
我给备考者的时间建议是:选择题控制在总时长的25%左右,编程题控制在50%左右,开放题或场景题控制在15%左右,剩下10%留作检查和补漏。
为什么把编程题排到一半时间?因为编程题是最容易拉开差距的部分。选择题四选一,瞎蒙也有25%正确率,但编程题如果通过率很低,直接决定你的笔试能不能过。
4.2 答题顺序:先拿稳分,再啃硬骨头
我自己的笔试策略是“三轮作答法”:
- 第一轮,快速浏览所有题目,把有把握的选择题先做完,遇到不确定的标记出来,不恋战。
- 第二轮,做编程题里思路最清晰的那道,先把暴力解写出来保证有分,再考虑优化。
- 第三轮,回头处理标记的选择题,并完成开放题或场景题。
这套顺序的核心逻辑是:把确定性收益先拿到手,再花时间去搏不确定的分数。很多同学喜欢按题目顺序一题一题做,结果在前面某道难题上耗掉40分钟,后面明明能拿分的题却没时间写,这是笔试现场最常见也最可惜的失误。
4.3 现场常见的五个失误
根据我带过的同学和大量复盘帖,校招笔试现场最常见的失误集中在五个方面。
第一,忽略输入输出格式。很多编程题对输入方式有明确要求,比如“第一行是数组长度,第二行是数组元素”,有时候还要求输出格式带逗号或空格。写代码前先用一小段样例手动模拟一遍输入输出,能省掉大量调试时间。
第二,边界条件没想清楚就动手。数组为空、k比数组还长、只有一种元素、全是负数,这些边界最好在写代码之前就列出清单。很多题目样例比较温和,但隐藏用例里全是边界情况。
第三,内存或时间超限。数据量不小的时候,O(n^2)的暴力算法大概率超时;开大数组但不释放,大概率超内存。写代码时就要对复杂度和空间占用有预估,而不是等到报错了再回来改。
第四,样例通过就急着交。本地样例只是最小验证,不代表所有用例都能过。留几分钟构造一两个极端用例,比如大数、重复数、越界数据,跑一遍再提交。
第五,代码风格太随意。笔试的编程题一般需要手写完整代码,如果变量名用a、b、c,函数没有缩进,注释缺失,即使算法对了也很容易被扣印象分。把代码写得像正常工程代码,是在规则内提高得分的有效手段。
5. 笔试之外:给下一届考生的备考清单
5.1 从“刷题”到“建体系”:知识覆盖比刷题量更重要
很多同学准备校招时喜欢追求刷题数量,今天刷5道,明天刷8道,看起来很努力,但遇到新题还是没思路。问题出在只刷题不归纳,没有建立知识体系。
我的建议是,按专题刷题比按题号顺序刷题有效得多。先把数据结构过一遍,数组、链表、栈、队列、哈希、树、图、堆,每个专题找代表性题目做透;再把算法思想过一遍,二分、双指针、滑动窗口、DFS、BFS、动态规划、回溯、贪心,每个思想用3到5道题吃透。
这个阶段的产出不是刷题数,而是一份自己的“题目类型地图”。看到一道新题,能快速判断它属于哪个专题、有哪些常见解法、每种解法的适用条件是什么,这才是笔试想要的能力。
5.2 针对快手业务特点的专项准备
回到快手本身。快手的核心业务是短视频和直播,所以在准备阶段,可以专门想一想下面这些场景在技术上怎么实现:
- 视频上传后,如何做转码和审核,审核失败如何通知用户。
- 一个视频的播放量、点赞量、评论量,如何在千万级QPS下准确计数。
- 用户关注关系如何存储,如何实现关注流和推荐流的合并排序。
- 评论回复这种树形结构,在数据库和缓存层如何设计。
- 直播弹幕这种高吞吐、低时延场景,适合用什么消息模型。
这些问题不一定直接出现在笔试题面上,但当你做过这些思考后,再看笔试卷子里的场景题和算法题,会多一层“这不就是某某问题的泛化”的敏锐度。这种从业务角度理解题目的能力,是普通刷题给不了的。
5.3 从笔试到面试的衔接
笔试结束不等于整个校招流程结束。从时间线来看,笔试通过后马上会进入面试阶段,不少人会在笔试后放松警惕,结果简历筛选和笔试都过了,却在面试里丢分。
面试和笔试最大的区别是:笔试看结果,面试看过程。面试官会盯着你在白板上写代码的过程,看你遇到卡壳时怎么反应,看你能否在提示下走通思路。所以准备面试时,要把重点放在“边做边说”的练习上,把每一步为什么这么想讲清楚。
另外,快手面试中,项目经历的深挖度很高。如果你简历上写了一个和业务相关的项目,面试官会一直追问到你说“这个细节我确实没考虑过”为止。这种追问其实很友好,它在考察你的诚实度、逻辑严密性和学习能力。在笔试之后的准备阶段,重新梳理自己做过的项目,把每个设计决策背后的理由想清楚,比再刷一百道题更值。
还有一点容易被忽略:笔试和面试之间的时间窗口,非常适合用来复盘笔试时写的代码。把当时写出来的代码重新拿出来,优化结构、补充边界处理、重新跑一遍测试用例,这个过程既巩固了知识,也在为面试中的手写代码做准备。我见过不少同学,笔试时代码写得凑合,面试前花两天认真复盘,结果面试状态明显提升。
回头来看这套2019年秋季的工程A卷,我最大的感受是:它不像有些公司的笔试题那样刻意追求难度,而是非常务实地在考察一个工程师的基本盘。数据结构掌握得牢不牢,网络协议理解得透不透,代码写得规不规范,遇到问题有没有自己的分析框架,这些能力不是考前突击能补出来的,而是长期写代码、长期思考积累的结果。
如果你正在准备快手或者其他大厂校招,我的建议是:不要把精力花在收集各种模拟题和押题上,而是老老实实把基础补扎实,把每个知识点背后的“为什么”想清楚,再通过适量刷题检验掌握程度。笔试只是校招的第一道门槛,但一套好的笔试试卷,往往能让你在准备过程中真正变强。这比最终拿到一个什么样的成绩,更有意义。