ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

百度2016研发工程师笔试题解析:C++细节与算法实战

百度2016研发工程师笔试题解析:C++细节与算法实战 2016年秋天我第一次坐在百度研发工程师笔试的考场里。试卷发下来那一刻我下意识先翻到最后那道编程题——这个习惯直到今天我都保留着。后来我刷过不少回忆版真题发现《百度2016研发工程师笔试题一》这套题几乎是当年大厂笔试的缩影C细节、数据结构、操作系统、网络再加一道开放设计题题量不大但每一道都在逼你露出真实的工程功底。这篇文章不打算给你一份“标准答案”而是想把这套题拆开来看哪些地方容易丢分答题时应该按什么节奏走考后又能从错题里挖出什么。1. 开考第一件事先搞清楚这套题的时间陷阱1.1 题型构成与分值分布我手上的版本是90分钟满分100分题型分为三类选择题、编程题、设计题。选择题里又分单选和多选单选15题、每题2分多选5题、每题2分这部分一共40分编程题两道一道20分最后一道设计题20分。不同场次可能微调但这个结构基本能代表当年的出题思路。题型题量/分值覆盖范围单选15题 × 2分C、数据结构、操作系统、网络、数据库多选5题 × 2分偏概念辨析、边界条件、易错结论编程题2题 × 20分链表/数组、动态规划/贪心设计题1题 × 20分短网址/缓存/系统设计方向单选和多选混在一起意味着你不仅要选对还要理解“为什么对”。尤其多选漏选、错选都不得分这是很多人考完对答案才发现大量丢分的地方。我后来倒推这套题的设计意图选择题是想快速筛选基础扎实的人编程题是想筛掉只会背题的人设计题则是为了看候选人有没有基本的工程视野。1.2 90分钟如何分配才不慌第一次做题时我犯过一个典型错误在单选题上反复纠结等做到编程题只剩二十分钟。后来我把这套题重新限时做了一遍总结出比较稳的分配方案拿到试卷先花2到3分钟通读全卷标记出没把握的题。这个动作非常重要它决定了你后面会不会被某道题拖死。选择题整体控制在35分钟以内。单题超过2分钟还拿不准先凭第一印象选一个并在题号上画圈有时间再回头。编程题每道留出20分钟左右总共40分钟。先写一个能跑的朴素解法再去优化。设计题最后处理留10到15分钟只写框架和关键点不需要展开成一篇小论文。这个节奏看起来很简单但真到考场上人会不自觉地陷进某个细节里。我建议你平时刷题就按这个倒计时练练到形成肌肉记忆考试时才能把脑力留给真正的难题。1.3 拿到试卷后先做哪一类顺序问题容易被忽视。我的偏好是先花2分钟看编程题题目如果其中一道思路立刻清晰就先写编程题如果两道都卡住就回头做选择题让大脑在后台继续处理编程题的思路。为什么要这样因为编程题分值高且最怕“来不及”。选择题即使最后蒙几个也有概率得分编程题如果没写出来20分就是实打实地没了。设计题放到最后倒不是因为它不重要而是它的得分点比较散时间不够也能写出核心框架能拿一部分分。这个策略我后来推荐给不少学弟学妹至少能帮他们避免“编程题整道空白”的惨剧。2. C题里那些“看一眼就会写就写错”的细节2.1 sizeof、数组名与指针一道选择题的完整推导这套题的选择题里有一道非常经典定义char str[] hello; char *p str;然后问sizeof(str)、sizeof(p)、sizeof(*p)分别是多少。先看答案sizeof(str)是6因为数组长度为5的字符串后面还有一个\0sizeof(p)在64位系统上是832位系统上是4sizeof(*p)是1因为*p是char。这里最大的坑在于很多人背过“数组名会退化成指针”于是想当然地以为sizeof(str)也是指针大小。但sizeof和取地址运算符是两个数组名不退化的场景。数组名在表达式中作为右值使用时才会退化成指向首元素的指针但sizeof是编译期运算符它拿到的是整个数组的类型信息。我当时在这道题上丢过分所以后来总结了一个记忆方法问sizeof时只要对象是数组名本身就要看数组类型定义的完整长度只有把数组名赋值给指针变量之后再sizeof指针才是平台相关的大小。2.2 虚函数、构造函数和析构函数的调用顺序另一道多选我记得很清楚基类Base和派生类Derived各有自己的构造函数和析构函数main里定义一个Derived对象问输出顺序。答案很简单先基类构造再派生类构造析构时先派生类析构再基类析构。但如果把题目改成这样难度就上来了基类析构函数不是虚函数然后写Base *p new Derived(); delete p;问会发生什么。结果是只调用基类析构函数派生类的析构函数不会被调用派生类中申请的资源就会泄漏。这就是为什么基类析构函数应该声明为virtual。这道题真正想考察的不是你能不能背出顺序而是你是否理解对象生命周期里“构造由内向外析构由外向内”的机制以及多态删除对象时的潜在风险。在笔试里这种题往往是多选选项里会混着“派生类析构一定会被调用”这种看似理所当然的说法。2.3 我在C题上丢过的分三条具体教训丢过分之后我整理了几条C易错点这套题或多或少都会覆盖到。const int *p和int *const p的区别。前者是“指向常量的指针”指针本身可以改后者是“常量指针”指针本身不能改。多选里经常配合*p 3、p这类操作来迷惑人。strcpy和memcpy的区别。前者遇到\0停止后者按字节数复制。如果源和目的内存有重叠memcpy的行为是未定义的应该用memmove。这套题里有一道多选专门挖了这个坑。有符号char的比较陷阱。默认char可能是带符号的char c 0xff; if (c 0xff)条件不成立因为c被提升成整型时是-1而不是255。这类题不做一遍真题很难凭直觉答对。C部分考得细但都不是偏题怪题全是对基本功的深挖。想拿分建议把《Effective C》里关于构造、析构、赋值的章节翻一遍再配合真题验证。3. 算法题与数据结构题把考场上的思考路径拆给你看3.1 从一道链表题说起快慢指针是怎样炼成的这套题编程题里有一道很典型的链表题输入一个链表输出该链表中倒数第k个结点。很多人的第一反应是先遍历一遍得到链表长度再走n-k步。这个解法没错但只能算基础分。更优的做法是双指针也叫快慢指针。快指针先走k步然后快慢指针一起走当快指针走到链表尾部时慢指针正好指向倒数第k个结点。整个过程只遍历一遍空间复杂度是O(1)。ListNode* findKthFromEnd(ListNode* head, int k) { if (!head || k 0) return nullptr; ListNode *fast head, *slow head; for (int i 0; i k; i) { if (!fast) return nullptr; fast fast-next; } while (fast) { fast fast-next; slow slow-next; } return slow; }这里有两个边界条件必须写清楚k大于链表长度时要返回空指针k等于0时也应该返回空指针。我在练习时发现很多人在白板上能写出核心逻辑但漏了这些边界判断结果线上判题只给部分通过。3.2 TopK问题堆排序和快速选择怎么选另一道编程题是海量数据求TopK典型问法一亿个整数中找出最大的100个。这道题很能拉开差距因为解法不止一个且不同解法对应不同的场景。最常用的方案是小顶堆。维护一个大小为100的小顶堆遍历所有数据如果当前元素比堆顶大就弹出堆顶把当前元素放进去。最终堆里就是最大的100个。时间复杂度是O(N logK)内存只用了K的空间。priority_queueint, vectorint, greaterint pq; // 小顶堆 for (int x : nums) { if (pq.size() K) { pq.push(x); } else if (x pq.top()) { pq.pop(); pq.push(x); } }另一个方案是快速选择基于快排的 partition平均时间复杂度是O(N)但有两个前提数据能一次性放进内存并且可以修改原数组。海量数据场景下数据在磁盘上或者不允许改变原数组快速选择就不合适了。我在答题时会先写小顶堆然后在注释里补一句如果内存允许且可以原地修改也可以用快速选择达到平均线性时间。这样做的好处是让阅卷人看到你懂权衡而不只是背模板。3.3 编程题答题时的“隐形加分项”代码写对只是及格线真正能让你在编程题上拿高分的是下面的习惯先写注释描述算法思路再写代码。笔试系统如果没法运行注释就是阅卷人判断你思路的窗口。写完整后自己在脑子里跑三个用例正常用例、边界用例空链表、k0、非法用例k超过链表长度。在代码末尾写清楚时间和空间复杂度。很多面试官会专门看这行即使笔试阶段不评分它也是一个正向信号。编程题不是看你写得多花哨而是看你稳不稳。能用最稳妥的思路在20分钟内写出可通过边界用例的代码远比追求一个炫酷但容易写错的最优解更划算。4. 操作系统与网络题用推演代替死记4.1 死锁题四个必要条件到底怎么用操作系统部分有一道很经典的安全序列判断用银行家算法。题目会给出一组进程每个进程有已分配资源、最大需求资源系统还有一部分可用资源问当前状态是否安全。这类题不需要背代码只需要会列一张表进程已分配最大需求还需P0253P1121P2363P3132先算出每个进程还需要多少资源再看系统当前剩余资源能满足哪个进程的需求。按这个顺序依次“执行”进程并回收资源如果能找到一条安全序列就是安全的否则就是不安全可能发生死锁。这道题对应的理论考点是死锁四个必要条件互斥、持有并等待、不可剥夺、循环等待。多选里经常让你判断哪些条件被破坏后可以预防死锁。但我发现光背条件没用必须亲自手算一道银行家算法考场上才能不慌。4.2 TCP三次握手为什么不能是两次网络题里必考三次握手。最常见的问法是“为什么需要三次握手两次行不行”。标准答案的关键点是防止已失效的连接请求突然传到服务器导致服务器建立无用连接并浪费资源。举个例子客户端第一次发起的SYN报文在网络中滞留了很久客户端等不到确认超时重发了一次这次正常完成了连接。等数据交换结束连接关闭后那个滞留的旧SYN报文才到达服务器。如果只有两次握手服务器收到旧SYN后会直接进入ESTABLISHED状态一直等待客户端发数据白白占着资源。三次握手时服务器在收到SYN后回复SYN-ACK但客户端此时不会对这个旧请求再发ACK所以服务器收不到确认就知道这个连接请求已经失效。这套题还会顺带考察状态迁移客户端从SYN_SENT到ESTABLISHED服务器从LISTEN到SYN_RCVD再到ESTABLISHED。答题时能把状态迁移表画出来比单纯写“三次握手”更得分。4.3 复盘时我意识到的两个理解误区第一次复习这套题时我踩过两个理解误区后来才发现之前根本没学透。一个是在四次挥手里总记混谁是主动关闭方。其实主动关闭的一方在发送FIN后进入FIN_WAIT_1收到对端ACK后进入FIN_WAIT_2等收到对端的FIN后进入TIME_WAIT等待2MSL后才关闭。被动关闭方收到FIN后进入CLOSE_WAIT发出FIN后进入LAST_ACK。把双方状态分开画就不会混了。另一个是信号量和PV操作题一开始总想套模板。后来发现只要把“资源数量”和“等待队列数量”画出来每次P操作资源减一S操作资源加一资源小于0时表示有进程在等待所有题都能推出来。模板只是在推演熟练后的自然结果而不是背出来的。5. 开放设计题没有唯一答案但有得分点5.1 短网址系统从一道真题看设计题答题结构这套卷子的设计题是短网址系统用户输入一个长URL系统返回一个短URL访问短URL时重定向到原始长URL。看起来简单但想拿高分需要按这套结构来第一步明确需求边界。需要支持自定义短码吗短码会过期吗每天大概新增多少条访问量是读多写少吗这些不确定时先按最常见的场景假设每日新增千万条读多写少短码永久有效。第二步做容量估算。7位短码用62进制0-9、a-zA-Z可以表示62^7个网址大约是3.5万亿足够应对未来十几年。所以短码长度定为7位合理。第三步设计存储。用一个发号器生成全局唯一ID比如Redis的INCR或数据库自增ID再把ID转成62进制字符串作为短码。存储上用Redis做热点缓存MySQL做持久化短码作为主键长URL作为字段。第四步想清楚重定向状态码。用301是永久重定向浏览器会缓存服务端压力小用302是临时重定向方便做点击统计。真实系统往往选302因为运营需要知道链接被点了多少次。5.2 我的答题草稿功能需求、存储设计、并发估计我在答题时会在草稿纸上画这样一个简表模块方案发号器Redis INCR 或数据库发号表保证ID唯一短码生成ID转62进制补齐到7位写入链路生成短码后写DB同时写Redis缓存读取链路访问短URL时先查Redis再查DB过期策略可选TTL热点链接续期重定向302服务端记录点击日志并发估算也很重要。如果每天新增1000万条平均每秒大约115个写请求并不高但读请求可能是写请求的几十倍所以必须加缓存。用Redis扛热点DB扛全量基本就能撑住大多数场景。这道题没有标准答案但你的草稿里如果出现了“每秒写入量”“缓存命中率”“DB分表”这些词阅卷人就知道你不是第一次做设计了。5.3 这道题真正想考察的三件事设计题跟编程题最大的区别是它考的是你拿到一个模糊问题时的拆解能力。第一会不会澄清需求。题目只给一句话你需要自己补充边界条件。第二有没有容量估算。随口说“用Redis缓存”不算数能算出量级才是真懂。第三有没有扩展意识。单机发号器挂了怎么办用数据库自增ID会不会成为瓶颈这些不一定都要写出来但提一句“发号器可以做成独立服务保证高可用”就能拉开差距。我当时在这道题上虽然只写了不到一页但因为抓住了“发号器缓存302重定向”这三个要点得分比想象中好。所以设计题不要怕写得短关键是写中的全是得分点。6. 考后复盘这套题里藏着的备考方向6.1 对比其他年份真题考点变化刷完2016这套题后我又找了几套后续年份的题目做对比。整体感觉是基础题型的比重一直很稳定C、数据结构、操作系统、网络是永远不变的四块但后续年份开始加入更多和岗位方向相关的内容比如机器学习、大数据处理、分布式一致性等。年份侧重点变化信号2016C细节、经典算法、基础系统设计强调基本功2017增加了更多工程场景题、设计题更开放开始关注工程综合能力2018算法题难度上升部分岗位出现ML基础题岗位分化明显所以如果你现在才开始准备只刷2016这一套肯定不够但用它打底非常合适。它能帮你快速判断自己基础薄弱点在哪。6.2 针对这套题我建议的复习顺序我自己的经验是按“真题摸底 → 模块补漏 → 编程题保持手感”的顺序推进。先用一个周末限时做一遍这套题把错题按知识点归类。然后针对错误最多的模块去补基础比如C的虚函数、内存布局或操作系统的死锁、调度算法。不要平均用力哪块丢分多就先补哪块。最后每周保持至少两道完整编程题的手写练习重点练链表、二叉树、动态规划、TopK这些高频题型。补基础时我推荐以教材为主视频为辅。看视频很容易产生“我懂了”的错觉但真正做题时才发现细节全忘。这套题里C部分就是很好的试金石能让你对自己的掌握程度有个清醒认知。6.3 笔试现场的一个小习惯留出检查时间最后分享一个小习惯。我第二次刷这套题时刻意把所有题的答题时间控制在75分钟留出15分钟检查。检查时不是逐题重做而是先看选择题里画圈的题再看编程题的边界条件最后看设计题有没有漏掉关键模块。这个习惯在真实笔试中帮我救回过至少两道题的分数。比如编程题我检查时发现有一段代码忘了处理k0的情况还有一道设计题忘了写缓存层。这些都不是不会做而是时间压力下容易疏忽。这套题我到现在还会推荐给准备研发岗笔试的朋友。它没有偏题怪题却能准确暴露一个人基础是否扎实、代码是否稳、设计思路是否清晰。如果你能把这套题吃透再去面对其他大厂笔试至少不会因为“没见过”而慌张。
返回列表