滴滴2016研发笔试题全解析:从TCP慢启动到派单系统
2026/8/29 21:16:56 网站建设 项目流程

1. 这套“老题”的整体设计与考察逻辑

1.1 先聊聊题量和模块分布

滴滴出行2016研发工程师笔试题(五),在公司校招题库序列里属于比较典型的“第四梯队”卷子——不算是难度封顶的那套,但覆盖面和陷阱密度都很有代表性。整套题一般是120分钟,客观题大概占30到40分,剩下是两到三道编程题和一道场景设计题。这种结构放到现在看可能觉得平淡,但在2016年前后,它恰恰代表了当时互联网公司筛选研发的标准姿势:基础课必须扎实,代码能力必须手写过关,同时还要求你能把技术落到业务场景里。

模块分布通常是这样的:计算机网络、操作系统、数据结构、数据库各出几道客观题,占分不算高但数量多;主观题部分有一道链表、一道字符串或动态规划,外加一道跟滴滴自身业务强相关的场景题。这种“基础+算法+业务”的三段式结构,其实是后来很多大厂笔试的模板,只是那时还没有那么多公司把题库标准化。

1.2 出题逻辑:不是难倒你,是过滤你

我后来自己也参与过几次校招命题,再看这套题,能明显感觉到出题人的意图不是“把所有人都考倒”,而是“快速过滤掉不适合做研发的人”。比如客观题里会埋一些常见的混淆概念,你如果只是背过名词解释,很容易在两个选项之间犹豫;编程题如果只会背题解,边界条件一改就会翻车;场景题更是没有标准答案,考察的是你拆解问题的思路。

这套题放到今天仍然值得拿出来做,不是因为题目本身有多新,而是它的考察粒度控制得很好:不要求你写红黑树,但要求你知道哈希表和二叉树在什么场景下选哪个;不要求你精通TCP所有细节,但要求你理解拥塞控制和高并发服务之间的关联。这种“考核颗粒度”是很多后来者没学到的——题不在难,在于能不能筛出真正有工程感的人。

2. 核心笔试考点逐题拆解

2.1 计算机网络:慢启动到底慢在哪

这套题里计算机网络部分的经典考法,是给你一段关于TCP拥塞控制的状态描述,然后让你判断慢启动阶段拥塞窗口的变化规律。题目大概是这样还原的:

在一个TCP连接中,发送方的拥塞窗口(cwnd)初始为1个MSS,经过一个RTT后变为2个MSS,再经过一个RTT后变为4个MSS。请问该阶段属于TCP拥塞控制的哪个阶段?拥塞窗口的增长方式是什么?

答案是慢启动阶段,增长方式是指数增长,也就是每经过一个RTT,拥塞窗口翻倍。很多人会在这里混淆“慢启动”这个名字,以为慢启动就是慢慢涨,其实恰恰相反,慢启动的“慢”是相对早期TCP直接注入大量数据而言的,从1个MSS开始,哪怕指数增长,前期也不会一下子把网络打爆。

这个考点在滴滴的业务背景下非常合理。滴滴的服务端要面对海量的长连接和短连接请求,尤其在早晚高峰,司机端和乘客端的实时通信非常频繁。如果TCP拥塞控制处理不当,网络拥塞会导致消息延迟,直接影响订单状态推送的及时性。所以考官出这道题,表面是考拥塞控制,实际上是在看你对高并发网络模型的底层机制有没有感觉。

答题时注意不要把慢启动和拥塞避免搞混。判断标准很简单:拥塞窗口小于慢启动阈值(ssthresh)时是慢启动,指数增长;达到或超过阈值后进入拥塞避免,改为线性增长(每经过一个RTT增加1个MSS)。题目如果给状态变化的具体数值,你就能很清晰地把这两个阶段区分开。

2.2 操作系统:进程线程,考的是能不能讲清“为什么”

操作系统部分的典型题目,是让你判断关于进程和线程的哪个说法是错误的。四个选项通常长这样:

  • A. 进程是操作系统进行资源分配的基本单位
  • B. 同一进程内的多个线程共享该进程的地址空间
  • C. 线程是处理器调度的基本单位
  • D. 线程是资源分配的基本单位

答案选D。进程才是资源分配的基本单位,线程是调度的基本单位。这道题的错误率其实不低,因为很多人记住了“线程轻量、进程重量”这个结论,但没搞清“资源分配”和“调度”分别对应谁。你只要记住一句话:线程共享进程的资源,自己不拥有独立资源,所以它不可能是资源分配的基本单位。

这道题背后还有一层意思,就是滴滴这种业务场景里,服务端大量使用多线程模型来处理并发请求。如果一个候选人连线程和进程的基本分工都说不清楚,那后面关于线程安全、锁竞争、上下文切换开销的问题就更没法聊了。考官出这题,是在给你的并发编程基础做一个底线体检。

另外有个容易忽视的点:如果题目追问“线程切换为什么比进程切换开销小”,你要能答出“因为同一进程的线程共享地址空间和大部分资源,切换时不需要切换页表”。页表切换是进程切换开销的大头,这个细节能说出来,说明你不是死记硬背。

2.3 数据结构与数据库:链表判环和索引命中

数据结构部分的经典题目是判断单链表是否有环,要求给出算法并分析时间和空间复杂度。标准解法是用快慢指针:快指针每次走两步,慢指针每次走一步,如果链表有环,两个指针一定会在环内相遇。时间复杂度O(n),空间复杂度O(1)。

这道题的易错点在于证明“快慢指针一定会相遇”而不是“可能相遇”。很多人代码能写出来,但问为什么一定会相遇就卡住了。简单说:慢指针进入环后,快指针已经在环内,每走一步,快指针相对慢指针逼近一步,所以最多跑一圈多就能追上。

数据库部分的题目通常会结合索引命中的场景,比如:

表orders有索引(user_id, status),执行SQL:SELECT * FROM orders WHERE status = 1,该索引是否会被使用?

答案是不会,因为不满足最左前缀匹配原则。索引最左前缀的意思是,查询条件必须从索引最左边的列开始连续匹配,跳过第一列直接用第二列过滤,会导致索引失效,走全表扫描。这个知识点在滴滴的订单查询场景里非常重要,运营后台经常要根据不同条件组合查订单,索引设计不合理,一个慢查询就能拖垮线上库。

2.4 场景题:派单系统背后考的不是算法,是权衡

整套题里最有滴滴特色的是场景设计题,原题大意是:系统需要把一笔新订单分配给附近的司机,要求给出可行的分配方案,包括数据结构、算法流程和复杂度分析。这类题目当年让很多只会刷LeetCode的候选人懵住,因为没有一个固定的标准答案,完全看你如何拆解。

出题人想看到的是这些层次:第一层,能不能想到用距离排序,取最近的司机;第二层,能不能意识到“最近”不等于“最合适”,需要考虑司机当前状态、接单意愿、服务分等因素;第三层,能不能把多因子加权、实时更新、高并发下的性能优化这些工程问题纳入方案。你回答得越有层次,说明你越接近一个真实的研发工程师,而不是一个单纯的刷题机器。

3. 编程题完整解法与代码实现

3.1 链表翻转:K个一组翻转链表的边界处理

这套题的编程题第一题,通常是K个一组翻转链表。题目描述很直接:给定一个链表,每K个节点一组进行翻转,不足K个的保持原有顺序,返回翻转后的链表。比如链表1->2->3->4->5,K=2时输出2->1->4->3->5;K=3时输出3->2->1->4->5。

解题思路分三步走。第一步,写一个辅助函数,翻转一段链表并返回新的头尾节点;第二步,遍历主链表,每数满K个节点就调用一次翻转函数;第三步,把翻转后的子链表和前后部分正确连接。难点不在翻转本身,而在边界条件的处理:链表长度为K的整数倍时怎么收尾,最后一组不足K个时怎么保持原序,以及头节点的更新。

我给出一个C++版本的参考实现:

struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* reverseKGroup(ListNode* head, int k) { if (head == nullptr || k <= 1) return head; ListNode dummy(0); dummy.next = head; ListNode* prev = &dummy; ListNode* start = head; ListNode* end = head; while (true) { int count = 0; while (end != nullptr && count < k) { end = end->next; count++; } if (count < k) break; // 翻转 [start, end) 区间的链表 ListNode* newHead = reverseBetween(start, end); prev->next = newHead; // start 翻转后成为这段的尾节点,接到 end 前面 start->next = end; // 移动 prev 和 start prev = start; start = end; } return dummy.next; }

这里的翻转区间是左闭右开区间,也就是说end指向的是下一组的第一个节点而不是本组的尾节点。这样设计的好处是,统一处理逻辑,不需要单独判断end是不是nullptr。翻转函数reverseBetween的核心写法是头插法,遍历[start, end)的每个节点,依次插入到新链表的头部。

实际笔试时,如果时间紧张,可以先写一个“翻转整个链表”版本的reverse函数,再在循环里裁剪出K个节点调用。这种写法虽然多了一些指针操作,但逻辑更直白,不容易出bug。我当年笔试就吃过亏,想用递归写,结果在返回条件的判断上卡了半天,浪费了不少时间。

3.2 字符串处理:最小覆盖子串的滑动窗口实现

第二道编程题常见的版本是:给定一个字符串S和一个字符串T,在S中找出包含T所有字符的最小子串。如果不存在则返回空字符串。比如S="ADOBECODEBANC",T="ABC",结果是"BANC"。

这是经典的滑动窗口问题,思路是:先用两个指针left和right维护一个窗口,right向右扩展直到窗口内包含T的所有字符,然后left向右收缩,在保持“包含T所有字符”的前提下尽量缩小窗口,记录最小长度和起始位置。

这里有一个关键优化点:统计T中每个字符的需求量,窗口内用另一个哈希表记录已包含的字符数量,再用一个变量matched记录有多少个字符已经满足需求。这个matched变量的作用,是把“判断窗口是否满足条件”从O(n)降到O(1),整体时间复杂度才能做到O(n)。

参考实现如下:

string minWindow(string s, string t) { if (s.empty() || t.empty()) return ""; unordered_map<char, int> need; unordered_map<char, int> window; for (char c : t) need[c]++; int left = 0, right = 0; int matched = 0; int start = 0, minLen = INT_MAX; while (right < s.size()) { char c = s[right]; right++; if (need.count(c)) { window[c]++; if (window[c] == need[c]) matched++; } while (matched == need.size()) { if (right - left < minLen) { start = left; minLen = right - left; } char d = s[left]; left++; if (need.count(d)) { if (window[d] == need[d]) matched--; window[d]--; } } } return minLen == INT_MAX ? "" : s.substr(start, minLen); }

这道题最常踩的坑有三个。第一个是匹配条件的判断,不要每次都重新遍历哈希表,要用matched变量实时维护。第二个是窗口收缩时,如果移出的字符是满足需求的字符,要先matched再减window计数,顺序反了会漏更新状态。第三个是返回值,用start和minLen记录最优解,最后再截取子串,而不是在滑动过程中反复调用substr,那样会引入不必要的开销。

有些同学会问,如果只要求“包含T的所有字符”而不要求顺序,那是不是也可以用数组替代哈希表?确实可以,如果字符集限定为英文字母,用int[128]的数组计数更快。但笔试时用哈希表更通用,不容易因为字符集扩展而翻车。

3.3 派单场景题:从“最近司机”到“综合评分”

编程大题之后的场景题,我建议你按下面的结构来组织答案,层次清晰,能拿高分。

先说最简单的方案:把每个司机的位置看成平面上一个点,新订单进来后,遍历所有空闲司机,计算欧氏距离,取距离最近的司机派单。数据结构就是数组或列表,时间复杂度O(n)。这个方案能答出来,说明你具备最基本的算法意识,但考官会追问:司机数量多了怎么办,比如一个城市10万司机,每秒几百笔订单,O(n)的扫描是扛不住的。

这时候你要主动抛出空间索引的概念。通常的做法是把城市划分成网格,每个网格内的司机用一个集合维护,查询订单时只需要在订单所在网格和相邻网格内搜索司机,可以把搜索范围从全城缩小到局部。进一步优化可以用四叉树或者GeoHash,GeoHash在业界用得很多,它把二维坐标编码成一维字符串,可以做前缀匹配,实现快速邻域检索。

再进一步,要回答“最近不是最合适”的问题。你需要提出多因子加权评分:距离、司机服务分、接单率、当前载客状态、前往接驾的路况预测,这些都作为评分因子,最终算出每个候选司机的综合分,取最高分派单。数据结构上,用最大堆(优先队列)维护候选司机列表,即可在O(log n)时间内取出最优司机。

我建议你在答案中明确写出流程:订单进入系统后,根据订单位置计算出候选司机集合,遍历集合计算评分并压入优先队列,弹出一个最高分司机,若该司机在几秒内未接单则回滚到第二高分,设置一个超时机制防止订单长时间无响应。这种结构化、有兜底方案的回答,才是出题人真正想看到的。

4. 常见错误与避坑经验

4.1 时间分配:在客观题上死磕是最亏的

那套笔试题120分钟,客观题占比不高,但分值再低也是分。最实际吃亏的是有些人在一道纠结的TCP题目上花了10分钟,导致后面编程题没写完。我当年一个很大的教训是:客观题第一感觉选完就过,标记拿不准的,最后如果有时间再回头看。因为客观题考察的是“熟练度”,你第一反应不会的知识点,再纠结5分钟也很难突然想通,反而会干扰后续的做题状态。

编程题的时间要留足。我给自己定的节奏是:做完客观题后剩90分钟,第一道编程题控制在25分钟,第二道控制在35分钟,剩下30分钟给场景题和复查。编程题先写核心逻辑,再补边界条件,而不是从第一步就开始纠结“万一根节点为空怎么办”,那样容易陷入细节里出不来。

4.2 代码边界条件:题目做对容易,全对难

笔试评分通常有多个测试用例在后台跑,边界条件是拉开差距的关键。链表翻转那道题,特别要注意“链表长度不足k”和“链表为空”这两个边界。我在代码里用了dummy节点来统一处理头节点更新的情况,这个技巧能省掉大量特判逻辑。

滑动窗口那道题,边界条件集中在字符串为空、T比S还长、T中的字符在S中不存在这三种情况。优雅的解法是用一个count变量记录“窗口中还缺多少个有效字符”,count不为0时说明窗口还没覆盖T,而不需要每次都遍历need表。

这里分享一个习惯:写完核心逻辑后,花30秒检查一遍异常输入。不是废话,很多候选人代码主体正确,但因为没有判空,三个隐藏用例直接挂了,非常可惜。写代码前先想好“输入为空的返回值是什么”,这是专业和业余的分水岭。

4.3 场景题:别只谈算法,要谈系统

场景题丢分最严重的情况是“通篇只讲了一个算法”。比如题目问“如何为新订单分配司机”,有人只回答“用KD树求最近邻”,然后就停了。这个答案不是错,但只有算法,没有系统。真实系统中还要考虑缓存怎么更新、司机位置上报的延迟怎么处理、订单和司机之间怎么防止重复匹配、派单失败后怎么降级。

我建议的答题框架是:先讲离线处理还是在线处理,再讲数据结构选型,然后讲具体算法流程,最后讲容错。比如你可以说“司机位置通过长连接实时上报,服务端聚合后写入Redis缓存,缓存采用过期策略保证数据新鲜度;订单进来时先查缓存,再通过GeoHash找出候选司机,利用评分模型排序后派单;如果司机5秒未接单,系统自动派给下一位候选司机,同时将原司机短期加入黑名单防止再次派单”。这样一段话就把数据流、存储、算法、兜底都讲全了,评卷人能直观感受到你有工程思维。

5. 这套题背后的技术栈与备考启示

5.1 从题目反推滴滴的业务形态

2016年的滴滴正处于补贴大战和业务飞速扩张的时期,系统要支撑的不仅是庞大的用户量,还有高频的实时定位、订单匹配、价格计算、支付回调这些核心链路。你把这套笔试题的考点串起来看,就会发现它们不是随机凑出来的:TCP拥塞控制对应的是司机端乘客端海量长连接的稳定性;进程线程对应的是高并发服务端的基础模型;索引设计对应的是订单库的查询性能;派单场景题对应的更是滴滴最核心的订单分发系统。

所以准备这类公司笔试的时候,不要只刷题,还要花点时间研究公司的业务形态。滴滴强调LBS相关算法和高并发架构,那你在复习时就要格外重视字符串、图论、动态规划这些高频算法,同时多想想算法怎么落到真实系统里。面试官问“为什么考这个”,实际也是在问“你能不能理解我们为什么在乎这个”。

5.2 备考建议:基础课复习的权重比想象中高

很多人准备校招笔试时,把90%的时间花在啃算法题上,结果到了考场发现,客观题里网络、操作系统、数据库的题才是最要命的。我的经验是,算法题决定你能不能进到下一轮,而基础题决定你是不是稳稳地把卷面分拿到手。客观题的分丢多了,编程题全对也救不回来。

我建议的复习配比是:时间大致按照算法50%、计算机网络20%、操作系统和数据库各15%来安排。计算机网络重点复习TCP三次握手四次挥手、拥塞控制、HTTP和HTTPS的差异;操作系统重点复习进程线程、死锁四个条件、进程间通信方式;数据库重点复习索引原理、事务隔离级别、最左前缀匹配。这些知识点做到看到题不动脑子能选出来,客观题这一关基本就没有威胁了。

5.3 做一套题不如拆一套题

最后多说一句。很多同学刷题数量不少,但效果一般,原因在于做完了就扔,既不复盘错题,也不尝试给题目做变形。这套滴滴笔试题我建议你按更高标准使用:第一遍正常做,对答案;第二遍只看题目,不看笔记,在纸上详细写出每道题的解题思路;第三遍试着把编程题改成变体,比如把“K个一组翻转”改成“每隔K个节点翻转一次”,把“最小覆盖子串”改成“最多包含K个不同字符的最长子串”,看自己能不能马上反应出解法。

这种拆解式的练习,比盲目刷二十道新题更有效。因为你练的不再是见过的题,而是可迁移的解题框架。框架到位了,面对没见过的题目,你也能快速拆解出错题人想考察的知识点。

我在实际带新人时,经常用这套2016年的题当练手材料。它不像现在的笔试题动不动就整复杂动态规划,而是用很有分寸的难度把候选人的基础功底和工程素养同时暴露出来。我见过刷遍了LeetCode的人在这套题上栽跟头,也见过基本功扎实的人花少量时间准备就高分通过。说到底,笔试考的不只是你会不会,更是你在压力下能不能稳定输出——这份稳定,才是从笔试题到真实研发工作之间最需要的能力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询