
2016年的滴滴出行研发工程师笔试题二在当年牛客网和各大求职论坛上算是流传度很高的一套题。那会儿正好是移动出行最热闹的阶段滴滴大量补研发笔试筛人很凶。我后来整理旧资料时把题重新过了一遍发现这套题放到今天依然值得细琢磨它不考偏题怪题而是用一种很“刁”的方式考基础——语言细节、数据结构、操作系统、网络、数据库再加一两道很有区分度的算法和逻辑题。很多准备校招的人刷过这套题但真正能把每一类题背后的出题逻辑讲明白的并不多。这篇文章就把这套题完整复盘一遍题型构成、高频考点、典型题目的解题思路、做题节奏、容易翻车的地方。不管你是正在准备笔试的应届生还是想用这套题自测基础是否扎实的在职工程师都能从中拿到点东西。我尽量按当年做题的真实场景来讲不绕弯子。1. 这套题的整体画像与出题逻辑1.1 2016年研发笔试的基本盘题型、题量与时间2016年互联网公司的校招笔试形态上大同小异线上笔试为主少数公司用纸质试卷时间是90到120分钟题量通常在40到60题之间。滴滴这套研发工程师笔试题二也遵循了这个框架整体由三大块组成选择题、填空题、编程题。选择题占大头覆盖C/C、Java、数据结构、操作系统、计算机网络、数据库基础填空题专门考察概念记忆的准确性比如某个关键字的作用、某条SQL语句的执行结果编程题一般放在最后一到两道考察的是综合编码能力不是随便写个冒泡排序就能糊弄过去的水平。“二”这个编号很关键。它意味着这套题不是孤立存在的前面还有一套“一”或者同期系列题。从我做过的多套互联网公司笔试题来看编号靠后的卷子整体难度会往上抬一截尤其是编程题和逻辑题的比重明显增加。考生如果只刷了“一”就来考“二”很容易在时间分配上栽跟头。一个比较典型的印象分水岭是选择题的送分题大概占三成剩下七成都需要动笔演算或者逐项排查。当年很多同学出来吐槽“选择题看着眼熟选起来全错”本质原因不是知识点没学过而是对概念的边界条件不熟悉。比如C语言里sizeof和strlen的区别、指针自增到底跳几个字节、TCP第三次握手丢了会怎样这些细节在教科书上都写了但平时写业务代码根本不会去碰一到考试就露馅。1.2 “二”这套题里藏着哪些考察维度把整套题的考察维度拆开可以发现出题人其实在筛两种能力工程落地能力和算法思维。第一层是语言与工程基础占比最高也是刷人最狠的。C/C的指针运算、内存布局、关键字语义static、const、volatile、结构体对齐Java的继承多态、异常机制、集合类底层结构数据库的索引原理、事务隔离级别、SQL执行顺序网络协议里TCP的状态迁移、HTTP方法语义。这些东西没有一项是“会不会背定义”能解决的出题人会在题目里埋好几个层级靠记忆做题的人往往第一个层级就被卡住。第二层是算法与逻辑思维。链表操作、二叉树遍历、排序与查找、动态规划、经典智力题这些内容在业务开发里用得不多但是面试官需要一个可量化的方式来评估候选人的思维质量。算法题就是最好的尺子。一道Top K问题能看出你是只会调库还是真懂快排的partition思想一道烧绳计时的逻辑题能看出你面对条件不充分的场景时能不能建立正确的模型。这两层考察维度合在一起恰好对应了出行平台研发团队的真实诉求既要能快速上手写业务代码又要能在订单量暴涨时设计出高并发、高可用的系统方案。2016年的滴滴对研发工程师的期待不是招一个只会写CRUD的码农而是希望候选人具备把复杂业务抽象成清晰模型的能力。1.3 出行场景对工程师的独特要求滴滴这套题和纯互联网公司的笔试题有一个明显差异部分题目带着业务背景尤其围绕LBS、派单、实时调度来出题。虽然题目表层还是在考技术知识点但背后其实在筛选“理解业务场景”的人。举个例子派单问题在业务上就是“多个乘客同时发单多个司机在不同位置系统要在几百毫秒内算出最优匹配”。这道题落到笔试题里可能会简化为一个算法题给一组司机坐标和一组乘客坐标求距离最近的匹配对。如果你在写答案时只谈排序和最小距离不考虑订单的时效性和地理位置索引那答案就停留在“会做题”的层面如果你能提到用空间索引比如网格索引或四叉树来缩小搜索范围甚至考虑到高并发下的缓存策略那你的答案就明显高出同龄人一个段位。所以我一直建议准备这类笔试的朋友做题时不要只把题目当题目多想一想“这个考点放在一家出行公司里解决的是什么实际问题”。这不是应试技巧而是你将来入职后真正要面对的工作方式。2. 基础题型的快速得分策略2.1 语言基础陷阱题指针、内存、默认参数的坑语言基础题是整套卷子的压舱石也是区分度最高的部分。先说C/C里的经典陷阱。指针运算几乎是必考的。int a[5]; int *p a;这个声明出来后p 1和a 1指向的位置完全不同。p 1指向a[1]这没啥争议但a 1跳过的不是4个字节而是整个数组的长度也就是5 * sizeof(int)。这个点很多人第一次做必错因为它考的是“数组名在表达式里的类型退化”和“指向整个数组的指针”这两个概念的差异。我建议做题遇到类似题目时先在草稿纸上画出内存布局图不要凭直觉选。结构体对齐也是高频考点。一个struct { char a; int b; char c; };在32位系统上sizeof不是6而是12取决于编译器默认对齐规则。出题人特别喜欢在这个地方设置干扰项因为很多人知道有对齐这回事但不知道对齐规则是“每个成员偏移量必须是成员自身大小的整数倍结构体总大小必须是最大成员大小的整数倍”。这种题只要你动手画一次内存布局就再也不会错。C的函数默认参数、重载决议、虚函数表也是选择题的重灾区。特别是“默认参数与重载同时存在时的调用结果”这种题考的是编译器怎么选择最合适的函数版本。Java方向的题目则集中在String、Integer等类的不可变性、ArrayList和LinkedList的底层结构差异、异常处理的执行顺序上。准备这部分没有捷径就是把每个知识点的边界条件抠清楚。2.2 数据结构与算法链表、二叉树、排序的必考点数据结构与算法的选择题考察范围相当稳定。链表相关的题目核心考点是“指针操作的顺序”。比如反转链表很多人背过代码但笔试不会让你写完整代码而是给你一段不完整的代码让你选出缺失的那一行。这种出题方式比手写代码更阴险因为它考察的是你对每一步指针变化的真正理解而不是代码的机械记忆。二叉树的选择题通常围绕遍历序列展开。给出前序遍历和中序遍历求后序遍历这是最经典的题型。解法的关键不是去背结论而是理解“前序遍历确定根节点中序遍历确定左右子树范围”这个递归过程。一旦你把这个过程内化了不管题目怎么变都能从容应对。”排序算法的选择题考的是性质对比稳定不稳定、时间复杂度的最好和最坏情况、空间复杂度。我建议把常用的八种排序整理成一个对照表做题时直接查表对照。但光背表还不够要知道为什么快排不稳定、为什么堆排序的空间复杂度是O(1)、为什么归并排序是稳定的。因为选择题经常变化措辞你不理解原理换个说法就又不会了。除了这些二分查找的边界处理、动态规划的状态定义也是常客。这些题目的共同点是看着不难但一做就错。原因在于大家平时写业务代码很少从零手写这些基础算法思维容易停留在“调用现成函数”的层面。2.3 操作系统与网络并发基础和协议细节的分寸感操作系统和网络在2016年的笔试中占比不算最大但却是很多人失分最惨的一块因为这两门课的知识点“听说过”和“真懂”之间隔着很远的距离。操作系统的必考点第一个是进程与线程的区别。教科书上的标准答案说“进程是资源分配的最小单位线程是CPU调度的最小单位”但笔试中常见的考法是给你一组说法让你判断哪些正确。比如“线程切换不需要操作系统干预”这个说法就是错误的因为线程切换依然会陷入内核除非是用用户态协程。第二个是死锁。死锁的四个必要条件互斥、持有并等待、不可剥夺、循环等待必须烂熟于心而且要知道“破坏任何一个条件就能预防死锁”对应的具体方案是什么。网络部分TCP的三次握手和四次挥手是每年必考。常见考法是给你一个状态序列让你选出正确的那条或者问“客户端发送FIN后处于什么状态”。HTTP协议通常围绕状态码和请求方法展开比如302和307的区别、POST和PUT的幂等性差异。这些细节平时开发时可能不敏感但笔试就是要考察你对自己每天用的协议栈到底理解到哪一层。做这类题有个技巧拿到题先判断考的是流程还是语义。流程类题目比如握手状态迁移直接画状态图语义类题目比如某个方法是否幂等一定要结合业务场景去推不要死记。2.4 数据库与SQL事务、索引和查询优化数据库题在2016年的笔试题里相对友好考点集中在事务的ACID特性、索引的数据结构和失效场景、常用SQL语句的语义和性能。事务题要特别注意隔离级别。四个隔离级别读未提交、读已提交、可重复读、串行化分别解决什么问题、会引发什么问题脏读、不可重复读、幻读一定要在理解层面上去记忆。一个常见的混淆点是MySQL默认的可重复读隔离级别下会不会出现幻读标准答案是不会因为InnoDB通过间隙锁解决了幻读问题。这种题考的是你对数据库实现的了解程度不是简单的背诵题。索引题围绕B树展开。为什么InnoDB用B树而不是B树是因为B树只有叶子节点存数据内部节点能存储更多索引项树的高度更低磁盘IO次数更少同时叶子节点之间有指针连接做范围查询时不用回溯到父节点。这个知识点必须能从头推一遍否则换个问法你就说不出所以然。索引失效是高频考点对索引列使用函数、隐式类型转换、模糊查询以%开头、多列索引不满足最左前缀原则。这些场景每个都要理解其底层原因。比如WHERE name LIKE %张为什么索引失效因为B树的叶子节点是按索引值排序存储的而%开头意味着不知道比较的起始位置无法利用有序性进行快速定位。把这些原因搞清楚了考试时不管出什么变体都能应对。3. 高区分度题目的详细拆解3.1 编程题无序数组Top K的三种解法编程题是整套卷子的压轴题也是区分度最高的。“二”这套题里的编程题网上考生回忆里出现频率最高的是Top K问题——从一个无序数组里找出第K大的数。这道题能考出非常多东西排序、分治、堆、复杂度分析全都在里面了。解法一直接排序。先把数组从大到小排个序然后输出第K个数时间复杂度O(n log n)。这个解法能得基础分但不会拿高分因为面试官期待的是更优的解法。解法二基于快排的partition思想。每次选一个基准值通过partition操作把数组分成两部分左边都大于基准右边都小于基准。然后判断基准值在当前序列中的位置和第K大的位置关系如果恰好相等就返回如果K在左边就在左半部分继续partition反之在右半部分。这种解法的时间复杂度期望是O(n)虽然最坏情况退化为O(n²)但通过随机选择基准值可以几乎避免最坏情况。import random def find_kth_largest(nums, k): def partition(left, right): pivot_idx random.randint(left, right) pivot nums[pivot_idx] nums[pivot_idx], nums[right] nums[right], nums[pivot_idx] store left for i in range(left, right): if nums[i] pivot: nums[i], nums[store] nums[store], nums[i] store 1 nums[store], nums[right] nums[right], nums[store] return store left, right 0, len(nums) - 1 target k - 1 while True: pos partition(left, right) if pos target: return nums[pos] elif pos target: left pos 1 else: right pos - 1解法三维护一个大小为K的最小堆。遍历数组当堆不满时直接入堆堆满后如果当前元素比堆顶大就替换堆顶并调整堆结构。遍历结束后堆顶就是第K大的数。时间复杂度O(n log K)空间复杂度O(K)。这种解法在大数据场景下更好用因为不需要一次性把全部数据加载到内存里。三种解法对比起来你就能理解为什么这道题能成为编程题里的经典它从考察排序基础到进阶算法设计一层层地筛选候选人。建议做编程题时把关键思路先写在注释里再动手写代码评分时即使代码有小bug思路正确也能拿不少过程分。3.2 逻辑题烧绳计时45分钟2016年的互联网公司笔试题还有一个很大的特色——智力题。滴滴这套题的“二”里出现了一道经典的烧绳计时题目给你两根不均匀的绳子每根烧完需要60分钟但任意一段的燃烧速度不一样让你用这两根绳子和一个打火机准确测出45分钟。这题的突破点在于“不均匀”三个字。因为绳子燃烧速度不均匀你不能把绳子折成四等分然后烧一份来代表15分钟那样的假设是不成立的。正确的思路是“同时从不同方向点燃”一根绳子同时点燃两头30分钟必然烧完因为两头向中间烧总速度相当于双倍。解法分两步。第一步第一根绳子同时点燃两头第二根绳子只点燃一头。30分钟后第一根烧完此时第二根也烧了30分钟剩下部分还能烧30分钟因为只点了一头。第二步立刻点燃第二根绳子的另一头。这时剩下来的绳子从两头同时烧15分钟后烧完。整个流程耗时30 15 45分钟。这个题的思维模型特别像分布式系统里的“冗余设计”单根绳子不可靠但通过同时操作多条绳子利用时间上的重叠来相互校准。我在复盘时发现面试官喜欢这种题是因为它能在很短的时间内考察一个候选人建模抽象的能力——你能不能从一个看似无解的条件里找到时间上的并行关系。这种题的另一个常见变体是“烧完一根绳子需要60分钟怎么测15分钟”解法思路完全一样只是步骤顺序调整一下。所以做题时不要只记住答案要理解“双向燃烧等于加速”这个基础模型。3.3 概率题两堆球怎么放胜率最高概率题在滴滴2016年的笔试题里虽然没有编程题那么重但一旦出现就是拉分的关键。“二”这套题里出过一道很经典的概率最优化问题有50个红球和50个蓝球你可以把它们随意分配到两个罐子里然后随机选一个罐子再从罐子里随机取出一个球。问怎么分配能让取到红球的概率最大最大概率是多少直觉想的人会随便均分觉得概率是50%。但实际上概率最大化的核心思路是“把不容易拿到的球全部塞到一个罐子里把容易拿到的球单独放在另一个罐子里集中暴露”。具体做法是第一个罐子放1个红球第二个罐子放剩余的49个红球和50个蓝球。这时选到第一个罐子的概率是1/2因为罐子里全是红球所以拿到红球的贡献是1/2乘以1等于1/2选到第二个罐子的概率也是1/2罐子里红球占49/99所以贡献是1/2乘以49/99。总概率是1/2 (1/2)(49/99) ≈ 0.747远高于50%。这道题本质上考察的是条件概率和资源分配的最优化思维。它不只是出一道数学题而是在模拟派单系统的资源分配逻辑——如何通过合理分区把稀缺资源集中调度提高全局目标命中率。做这类题时一定要先明确“决策变量是什么”“约束条件是什么”“优化目标是什么”然后在这个框架里寻找最优解。这道题还有一个变体如果要求两个罐子里都必须有球且罐子里的球数不限那最优解还是同样的方案。因为1个红球单独放剩余球全部放另一个罐子没有违反任何限制条件而且数学上已经证明了这是全局最优。4. 做题节奏与实战时间分配4.1 拿到卷子前10分钟做什么笔试一开始的10分钟决定你整场考试的走向。很多同学拿到卷子立刻开始按顺序做结果被前面的几道概念题卡住后面的编程题只能草草收尾。我建议的策略是先不急着动笔花5到10分钟把整张卷子快速浏览一遍。浏览的目的是摸清底细。看选择题的考点分布看填空题有没有不会的概念看编程题的题目类型和难度。然后在草稿纸上把题目划分为三档第一档是送分题一眼就能看出答案的第二档是中等题需要动笔推演但思路清晰的第三档是硬骨头需要深入思考或者计算量大的题。做题顺序严格按照先送分、再中等、最后硬骨头。这个策略的核心逻辑是笔试的分数不是按题目顺序分布的而是按难度分布的。如果你把大量时间耗在了一道3分的选择题上导致那道15分的编程题没时间写这属于典型的捡芝麻丢西瓜。我当年做这套题时先扫完卷子发现编程题是Top K心里就有底了因为我清楚partition方案需要多长时间写完。然后回过头去逐步做前面的题心态稳了很多。4.2 选择题的排除法反例比正面计算更快选择题普遍是四选一或五选二很多题目不需要你完整推导出正确答案用一个反例排除错误选项剩下来的往往就是答案。比如判断一个说法“所有递归都能改成迭代实现”这个说法看着像是真的但你可以直接想递归的本质是函数调用栈迭代需要用显式的栈来模拟虽然理论上任何递归都能改成迭代但有些递归改起来复杂度极高比如涉及回溯的递归。如果能快速想到一个难以用迭代实现的反例就能判断这个说法是否在主流语境下成立。排除法特别适合操作系统和网络的概念题。比如考死锁的必要条件你只要记住“互斥”这一条就可以去选项中把没有提到“资源不能共享”表述的排除掉考TCP的三次握手你只要记得“第二次握手的标志位是SYNACK”就能快速锁定选项。还有一个技巧多个选项里如果存在“两个选项意思完全相反”那么这两个选项里大概率有一个是对的因为出题人经常用这种方式设置干扰项。这时候回到知识点去做判断比逐项分析每一个选项要快得多。4.3 编程题的拆解、写码、验证三步法编程题不能拿到就写要有固定的执行流程。第一步是拆题把题目要求拆成输入、输出、约束条件三部分。第二步是设计算法用一个自然语言描述你的思路包括时间复杂度和空间复杂度。第三步才是写代码。写代码的时候不要追求一次性写对先保证骨架正确。我习惯先写函数签名和主要逻辑框架再填充细节。对于Top K这道题骨架就是递归/循环调用partition核心逻辑是判断基准值的下标和目标位置的关系。代码写完一定要验证。笔试环境通常没有编译器你只能手推测试用例。选一个普通用例比如[3, 2, 1, 5, 6, 4]K2预期答案是5。然后按代码逻辑手动走一遍partition看是否符合预期。如果有边界条件比如空数组、K1、K等于数组长度也要单独推一遍。这些边界条件往往是踩分点没有验证容易写下错误的代码。如果时间不够不要放弃编程题。即使只写出思路和关键代码片段评分时也能拿到部分分数。我在这套题的考试建议里反复强调留15分钟以上给编程题前面的选择题再纠结也要放一放。5. 复盘这套题对今天面试的指导意义5.1 基础不牢今天会死得一样惨很多人会觉得2016年的校招笔试题放到2024年已经没有参考价值了。但我在一线做研发这些年一个很深刻的体会是校招笔试筛人的底层逻辑从来没变过。今天的高并发服务、分布式存储、大数据计算底层全都是操作系统、网络、数据结构和算法。你写一个缓存组件时遇到的缓存穿透、缓存雪崩问题本质上就是操作系统里页面置换算法的业务版你排查一个线上接口超时问题最后发现根因是数据库索引设计不合理而那恰恰是笔试里考过的“索引失效场景”。所以这套题的价值不是让你刷完之后去应付十年前的面试而是用它来检查你对计算机基础知识的掌握是否扎实。如果你现在做这套题的正确率还不到70%那说明你的基础有漏洞写业务代码也许暂时感受不到但遇到线上故障排查、性能优化这类需要系统化思考的任务时漏洞就会暴露出来。5.2 面试官为什么还在问这些“老题”作为一个参与过招聘的工程师我很明确地告诉你面试官问老题不是偷懒是因为这些题目经过多年的验证信息量足够大能高效地识别候选人的能力。以Top K为例一个候选人如果只给出排序方案那么他可能“知道算法”但不知道“权衡”如果他能给出partition方案并主动分析最坏情况那说明他有复杂度意识如果他能进一步提出用堆来处理数据量很大的场景那说明他具备工程思维。同一道题三个层次的回答能非常直观地分出水平这就是经典题目长盛不衰的原因。逻辑题也一样它考察的是建模能力。我们做技术方案时每天都要把一个模糊的业务需求转化成清晰的技术模型比如“高峰期订单如何分配”要转化为“在约束条件下求解最优匹配”的数学模型。具备这种思维的人遇到任何新问题都能快速找到切入点不具备的人即使背了很多技术名词上手做项目时依然会手忙脚乱。5.3 用这套题的思路准备现在的面试如果你现在准备的是2024年或2025年的研发岗位面试完全可以沿用这套题的准备框架。第一步做一遍语言基础自测。把C或Java的核心语法、内存模型、并发编程的知识点过一遍最好是找一套带答案解析的笔试题逐题吃透。第二步手写一遍经典数据结构和算法链表反转、二叉树遍历、快排、堆排、二分查找每个都要能白板写出来并且解释复杂度。第三步把操作系统、网络、数据库的高频考点整理成自己的笔记不要抄书用你自己的话把每个概念讲清楚。这个框架的核心原则是把刷题当成一次知识体系的自我检查而不是为了答案本身。我见过很多候选人对“面试题”了如指掌但深挖一个点就露馅就是因为只背了表面答案没有构建底层逻辑。面试官特别擅长通过“换一个问法”来测试你到底是真懂还是假懂只有底层逻辑牢靠的人才能应对各种变体。6. 失分点复盘与独家避坑清单6.1 编程题最容易翻车的三个位置编程题的翻车位置我总结了三个高频区域。第一是边界条件。数组为空、K为0或K超过数组长度这三类情况必须单独处理。很多人在写Top K时只考虑正常情况没有写if not nums or k 0 or k len(nums)的判断导致边界输入直接报错。第二是partition的循环条件。我用过很多次快排partition最常写的bug是循环里忘记处理指针越界或者交换元素后忘记更新游标位置。第三是复杂度的误判。有些人把Top K写成每次都对整个数组排序时间复杂度是O(n log n)然后还自我感觉良好这就是典型的基础不牢。针对这三点我的建议是写代码前先用三分钟在草稿纸上列出你要处理的边界条件写partition时对照循环不变量来检查写完主逻辑后特意代入一个极端测试用例验证。6.2 概念题里最常被搞混的几组定义概念混淆是选择题失分最大的原因。第一组是“进程与线程”。很多人把线程理解为“轻量级进程”但这个表述不准确准确的说法是“线程是进程内的执行单元同一个进程中的线程共享地址空间而进程之间相互隔离”。第二组是“并发与并行”。并发是多个任务在同一个CPU核上交替执行并行是多个任务在不同CPU核上同时执行这两个词的层次完全不同。第三组是“TCP与UDP”。TCP不是“可靠的UDP”二者的根本区别在于连接状态、流量控制、拥塞控制、数据有序性等一系列机制的综合结果不能用一个“可靠”概括所有差异。我推荐一个复习方法每学完一组概念自己尝试用一句话说清楚两个概念的本质区别。比如问自己“GET和POST的本质区别是什么”想清楚了再去看教科书上的标准答案查漏补缺。这个方法比单纯做真题高效得多。6.3 实战心得时间不够时的正确取舍最后聊一个非常现实的场景时间永远不够用。整套卷子的题量常常超出正常做题速度这时候学会取舍比学会做题更重要。我个人的策略是性价比优先。一道选择题3分一道填空题2分一道编程题15分。如果在一道选择题上卡了五分钟果断跳过标记一下回头再说。编程题即使不能完全AC也要写出核心代码和思路这部分的过程分相当可观。有一个很真实的情况当年我考这套题时前面的概念题花了很多时间结果编程题只剩10分钟。我快速写了个排序方案的解题思路和局部代码最后拿了一半的过程分。如果我在前面死磕可能编程题完全没分整体差距就大了。另外做题时一定要保持节奏感。我给自己定过一个规矩每做完五道题看一眼剩余时间调整下一步的做题速度。如果时间比预期紧张就放弃那种需要大量计算的题目直接凭概念和常识去选把时间留给能稳稳拿分的题。这套方法不见得能让你考满分但能让你在有限时间内拿到最高总分这才是笔试的真正目标。我个人在实际操作中的体会是这套2016年的滴滴笔试题虽然年代久远但它的知识点覆盖和出题风格放在今天依然是很高质量的面试自测材料。做完之后不要只看对错把每道错题对应的知识点重新过一遍形成自己的错题本。这个习惯比单纯刷一百套题都管用。