
1. 从2017年百度的这套题说起每年三四月份各大厂的春招笔试就集中爆发对计算机相关专业的同学来说笔试成绩直接决定了你能否拿到面试门票。百度2017春招这套编程题在当年算是比较典型的代表难度适中、区分度好覆盖了排序、贪心、动态规划、枚举、字符串处理这些笔试最高频的考点。即便放到今天回头看这套题依然很有刷的价值——考点不过时题型风格也一直延续到现在。这套题一共6道做起来差不多两个小时题量不算大但每道题都有值得琢磨的细节。我自己带过不少同学刷题2017年这套题几乎人手一份因为它特别适合用来检验基础贪心什么时候该排序、动态规划怎么定义状态、枚举怎么控制边界每个点都戳在面试笔试最容易出问题的地方。这篇文章会把6道题逐一拆开每道题都会讲清楚三个层次题目在考什么、最优解怎么想到的、代码怎么写不会踩坑。还会额外补充一些我在真实笔试中的经验——这些才是平时刷题平台上看不到的。适合这样几类人阅读正在备战春招秋招的应届生、想系统巩固算法基础的在职开发者、以及需要一套高质量练习题用来带新人的团队leader。无论你现在处于哪个阶段这套题都能帮你找出自己知识体系里的薄弱环节。2. 整体风格与考点分布2.1 2017年百度笔试的出题风格先说说这套题的整体风格。6道题里模拟和思维题占了大头真正的难题后面也就一两道。百度跟其他大厂不太一样的地方在于它特别爱出“代码不复杂但思路要绕一下”的题——看着像能暴力解但一算复杂度就发现必须优化这种风格在这套题里体现得淋漓尽致。比如第2题“度度熊回家”题目给你一个整数序列表示每次移动的距离和方向问删掉其中一个数后从起点到终点走过的总路程最少是多少。这题第一反应可能是枚举删哪个数然后模拟整个移动过程复杂度O(n²)当时数据量小能过但百度的评测机向来卡得不留情面动态规划才是正解。这套题同时还考察了一个很重要的能力读题。好几道题的表述都有一定迷惑性比如“不等式数列”那题题目给了一个看起来很复杂的递推关系实际上一层循环就能解。笔试跟平时刷题最大的区别就在这里——没人给你点明考点你需要自己识别出这题在考什么、用哪套模板。2.2 六道题的考点分布与难度评估题目核心考点难度推荐做题时间买帽子排序 / 去重 / 思维★☆☆☆☆10分钟度度熊回家枚举 / 模拟 / 动态规划★★☆☆☆15分钟寻找三角形枚举 / 几何公式 / 浮点数处理★★★☆☆20分钟有趣的排序排序 / 最长连续序列 / 思维★★★☆☆20分钟不等式数列动态规划 / 组合数学★★★★☆25分钟进制转换进制运算 / 位操作 / 大数处理★★★☆☆20分钟从表格里能看出来这套题没有纯粹的模板题每道题都需要你在基础算法的基础上做一点变通。这也是我特别推荐这套题的原因如果你能在两个小时内独立做出5道以上你的基础应对绝大多数一二线厂的笔试题没什么问题了。难度控制上百度当年的策略是“第一题送分最后一题压轴”中间几道题拉开差距。也就是说你至少要快速拿下第1题建立心态才能有充足的时间啃后面的硬骨头。我见过太多同学在前两题上磨蹭太久导致后面会做的题都没时间写。2.3 这套题对当前备考的参考价值可能有人会想2017年的题现在都这么多年了还有参考价值吗我的答案是有而且很大。笔试考点不像技术栈那样迭代那么快排序、贪心、DP、枚举这些基础题型到什么时候都是笔试主力。你去看最近两年各大厂的笔试真题很多题的核心思路还能追溯到2017年这套题上。另外一个重要原因是这套题的数据范围和限制条件设置得非常典型足够让你练习“根据数据范围猜测算法类型”这项关键能力。数据范围是10³还是10⁵直接决定了你需要用O(n²)还是O(n log n)的算法这是实打实的笔试实战技巧平时刷题不刻意训练的话很容易忽视。3. 六道真题逐一拆解3.1 买帽子——最短的题往往暗藏陷阱题目原意是度度熊想去商场买一顶帽子商场里有N顶帽子有些帽子的价格可能相同。度度熊想买一顶价格第三便宜的帽子问第三便宜的价格是多少不存在则输出-1。这道题看起来简单到不能再简单了但它的陷阱恰恰藏在“有些帽子的价格可能相同”这句话里。如果直接排序后取第三个元素遇到重复价格就会出错。n int(input()) prices list(map(int, input().split())) # 方法一排序后去重 prices.sort() unique_prices [] for p in prices: if not unique_prices or unique_prices[-1] ! p: unique_prices.append(p) if len(unique_prices) 3: print(-1) else: print(unique_prices[2])这个方法的时间复杂度是O(n log n)主要开销在排序上。还有一种写法是用集合去重然后转成列表排序代码更简洁但本质思路一样。为什么这道题值得单独拿出来说因为在真实笔试中这种“送分题”恰恰是翻车重灾区。我见过不少同学思路完全正确但没注意去重或者没处理“不存在”的情况白白丢分。笔试的判题是数据驱动的边界情况一个不处理好就可能只过部分测试用例。我的建议是做这类简单题也要走完整流程先把样例跑通、再想边界情况空数据、全部重复、只有两个不同价格等、最后提交。简单题拼的不是智商是细心程度。这道题你省下的时间就是后面难题的思考时间。3.2 度度熊回家——枚举与动态规划的路线之争这道题的原题表述是一个数轴上共有N个点第一个点的坐标是度度熊现在位置第N-1个点是度度熊家的位置。现在度度熊需要依次从第1个点走到第N个点但是他可以选择跳过其中某一个点问跳过哪个点可以使得度度熊走路的总距离最短输出最短距离。首先明确一点题目说的是“依次从第1个点走到第N个点”也就是说正常路径是从点1走到点2、点2走到点3……一直走到点N总距离是相邻点距离的累加。允许跳过其中某一个点问最短总距离。解法一暴力枚举最直观的思路就是枚举删除哪个点。删除第i个点后原来从i-1到i的路径和从i到i1的路径被替换成从i-1直接到i1的路径所以总距离的变化量是|pos[i1] - pos[i-1]| - (|pos[i] - pos[i-1]| |pos[i1] - pos[i]|)对每个i计算总距离取最小值即可。复杂度O(n)。如果你用更朴素的“删除后重新模拟走路”的方法每次删点后重新累加距离那就是O(n²)在n比较大的时候有超时风险。我当时在做这道题的时候第一次就是写的O(n²)版本虽然当时能过但这个习惯不好——在笔试里一个能优化的地方都要优化因为评测机不会每次都那么宽容。#include bits/stdc.h using namespace std; int main() { int n; cin n; vectorint pos(n); for (int i 0; i n; i) cin pos[i]; int total 0; for (int i 1; i n; i) { total abs(pos[i] - pos[i-1]); } int ans total; for (int i 1; i n - 1; i) { int cur total - abs(pos[i] - pos[i-1]) - abs(pos[i1] - pos[i]) abs(pos[i1] - pos[i-1]); ans min(ans, cur); } cout ans endl; return 0; }解法二动态规划这道题如果拓展一下问“允许跳过K个点求最短距离”那枚举就失效了必须要用DP。定义dp[i][j]为走到第i个点、已经跳过了j个点时走过的最短距离状态转移为dp[i][j] min(dp[i-1][j] abs(pos[i] - pos[i-1]), dp[i-2][j-1] abs(pos[i] - pos[i-2]))前者表示从i-1正常走到i后者表示从i-2跳过i-1走到i。这道题只要求跳过1个点所以用枚举就够了。但如果你在笔试里看到这个题目可以多想一步出题人会不会把题目改个条件让你用更高级的算法这种提前思考的习惯能帮你在真正的难题面前快速找到方向。这道题我踩过的坑忘了考虑点可以重合的情况。题目并没有说坐标互不相同如果存在重合的点跳过某个点前后距离可能不变。虽然不影响最终答案的正确性取最小但会让我在心里嘀咕是不是算错了。做笔试的时候心态稳定很重要。3.3 寻找三角形——浮点数精度与几何公式第三题是这样的二维平面内有N个点每个点有一个颜色红色/绿色/蓝色颜色用字符R、G、B表示。现在要从中选出3个点组成一个三角形要求这3个点的颜色两两不同或者完全相同求满足条件的三角形最大面积输出面积值保留5位小数。如果没有满足条件的三角形输出0。这道题有两个关键点一是枚举所有三点组合N通常不大N≤50就能过O(N³)二是面积计算用海伦公式还是向量叉积。为什么力推向量叉积而不是海伦公式海伦公式需要先算三条边的长度然后用sqrt(p * (p - a) * (p - b) * (p - c))计算面积。公式本身是对的但存在两个隐患第一浮点运算次数多误差累积更明显第二如果三点接近共线但并不是完全共线海伦公式里的p - a、p - b、p - c可能因为浮点误差变成极小的负数开根号直接报错。我在实际运行中真的遇到过这种情况调试了很久才发现是浮点精度的问题。向量叉积的方法则优雅得多。对于三个点(x1, y1)、(x2, y2)、(x3, y3)面积等于area 0.5 * abs((x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1))这个公式的几何意义是以第一个点为基准向量AB和向量AC构成的平行四边形的面积的一半。计算过程中只有乘法和减法浮点误差小得多而且三点共线时叉积恰好等于0天然能处理共线情况。def area(p1, p2, p3): x1, y1 p1[0], p1[1] x2, y2 p2[0], p2[1] x3, y3 p3[0], p3[1] return 0.5 * abs((x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1)) n int(input()) points [] colors [] for _ in range(n): parts input().split() colors.append(parts[0]) points.append((int(parts[1]), int(parts[2]))) max_area 0.0 for i in range(n): for j in range(i 1, n): for k in range(j 1, n): if colors[i] colors[j] colors[k] or (colors[i] ! colors[j] and colors[i] ! colors[k] and colors[j] ! colors[k]): max_area max(max_area, area(points[i], points[j], points[k])) print(f{max_area:.5f})常见问题一三点共线能不能算三角形题目说选出3个点组成三角形从几何定义出发共线三点不能构成三角形。用叉积算出来的面积为0不会影响最大值。常见问题二如何判断“颜色两两不同”三个字符两两不同其实就是三者的集合大小为3写成c1 ! c2 and c2 ! c3 and c1 ! c3最直观。注意千万不要写成c1 ! c2 ! c3这种链式比较在Python里虽然语法合法但语义是(c1 ! c2) and (c2 ! c3)漏掉了c1 ! c3的判断极容易出bug。浮点输出注意事项题目要求保留5位小数Python里直接用f{max_area:.5f}C里用printf(%.5f\n, max_area)。如果答案是整数比如面积为25输出也会自动补成25.00000这点不用担心格式化函数会处理。3.4 有趣的排序——思维题的精髓在“反向思考”这道题很有百度的风格题目是度度熊有一个长度为N的数组他想将该数组从小到大排序但是度度熊只会以下操作任取数组中的一个数然后将它放置在数组的最后一个位置。问最少进行多少次操作能将数组变为有序。这题看起来像排序题实际上是一道思维题。直接模拟每次取出一个数放到末尾复杂度太高且难以确定最优策略。正确的打开方式是反向思考找出数组中最长的连续上升子序列的长度答案就是n减去这个长度。为什么我们换个角度想既然我们只能把元素移到末尾那么保持不动的元素一定是在最终排序完成后相对位置不变的那些。在一个已经有序的数组中保持相对顺序不变的元素在原数组中一定构成一个连续上升子序列这里的连续指的是在原数组中顺序递增但值不必相邻。我们想让移动次数最少就要让保持不动的元素尽可能多。而这些不动的元素在原数组中的顺序必须和它们排序之后的顺序一致。换句话说我们需要在原数组中找到一个最长的子序列它本身已经是递增的而且这些元素在排序后的数组中的相对位置和原数组中的相对位置完全一致。这里有一个重要的细节这个“最长递增子序列”必须是连续取值的。什么意思呢比如原数组是[3, 1, 2]最长的连续上升子序列是[1, 2]长度为2答案是1。把3移到末尾就得到了[1, 2, 3]。但如果我们找的是最长递增子序列LIS[3]或[1, 2]都是但[1, 3]在原数组中的顺序是1在3后面不行。所以这道题要找的实际上是在原数组中位置和值都递增的最长子序列且这个子序列中的数排序后也紧挨着——这等价于“数值上连续递增的最长子序列”。举个例子来说清楚原数组[2, 1, 4, 3, 5] 排序后[1, 2, 3, 4, 5]在原数组中[1, 3, 5]是递增的但它们排序后不相邻1和3之间隔着2所以不能保持不动。真正能保持不动的是排序后在目标数组中也相邻的那一串数。在[2, 1, 4, 3, 5]中[1, 3, 5]不满足[2, 4]也不满足排序后2和4中间隔着3但是[1, 3]在原数组中位置依次递增、数值也递增且相邻也不对排序后1和3中间隔着2。所以正确做法是排序后原数组中的每个元素都有一个“排序后的位置”。我们要找的是那些在原数组中位置递增、且排序后位置也递增且连续的一段。这个问题的标准解法是把原数组排序记录每个值排序后应该在的位置然后遍历原数组找出在原数组顺序中“排序位置连续递增”的最长段。更直观的做法是因为是1到N的排列或者可以去重后的数组我们可以用一个map记录每个数在原数组中的下标然后从1开始逐个往后找看map[i1]是否大于map[i]。如果大于说明这两个数在原数组中的相对顺序是1在2前面排序后也能保持这个顺序可以都不动。如果小于说明i1在i前面必须动其中一个。这样遍历一遍就能找到最长连续递增子序列的长度。#include bits/stdc.h using namespace std; int main() { int n; cin n; vectorint a(n); unordered_mapint, int pos; for (int i 0; i n; i) { cin a[i]; pos[a[i]] i; } int max_len 1, cur_len 1; for (int i 1; i n - 1; i) { if (pos[i 1] pos[i]) { cur_len; max_len max(max_len, cur_len); } else { cur_len 1; } } cout n - max_len endl; return 0; }为什么是n - max_len因为找到能保持不动的连续上升段之后剩下的所有数都至少需要移动一次把它们依次放到末尾最终就能排成有序。这里的最少操作次数恰好等于需要移动的元素个数。你可能想问会不会存在一种更优的方案移动操作能“顺带”把多个元素送到正确位置不会因为每次只能把一个数放到末尾而且放到末尾后它可能又被其他操作覆盖必须单独处理。放在末尾的元素会一个接一个排好但不能减少需要移动的数的总数。这道题给我最大的启发是面对“操作次数最少”的问题不要只想着模拟操作过程先想一想哪些元素可以不动。一旦确定了不动的元素答案往往就呼之欲出了。这种反向思考的能力是编程思维中特别重要的一环。3.5 不等式数列——动态规划的经典状态设计这道题相对偏难一些。题目意思是度度熊最近对全排列特别感兴趣对于1到n的一个排列度度熊发现可以在中间根据相邻两个数的大小关系插入适当的大于号或小于号例如一个排列1, 3, 2因为1332所以可以插入为 132使得不等式成立。现在度度熊想知道对于1到n的排列有多少个排列使得这些不等式中有k个小于号答案对2017取模。这道题的关键在于状态的定义用dp[i][j]表示由1到i这i个数组成的排列中恰有j个小于号的排列个数。核心转移思路当我们从1到i-1的排列扩展到1到i的排列时把数字i插入到已有的排列中。数字i是当前最大的数因此当它插入到某个位置时会产生一些有趣的变化如果它插入到排列的最开头那么它和原来的第一个数之间会产生一个大于号因为i最大所以小于号的数量不变。如果它插入到最末尾那么它和原来的最后一个数之间会产生一个小于号小于号数量加1。如果它插入到排列中间的两个数字之间会有两种可能原来那个位置如果是一个小于号插入i后会变成大于号因为i最大小于号数量减1原来那个位置如果是一个大于号插入i后还是会变成大于号小于号数量不变。但要注意插入i还会在它左右两侧各产生一个新的关系左侧与左邻居、右侧与右邻居这个过程需要仔细推导。更简单直观的推导方式是**把数字i插入到一个由1到i-1组成的排列中一共有i个空位可以插。**设原排列中有j个小于号那么有(j1)个位置插入i后小于号数量不变最开头的位置以及每个小于号的位置插入后代替原来的小于号有(i-j-1)个位置插入i后小于号数量加1最末尾的位置以及每个大于号的位置插入后代替原来的大于号。这个推导过程是经典的“插空法”也是这类DP的核心套路。状态转移方程就出来了dp[i][j] dp[i-1][j] * (j 1) dp[i-1][j-1] * (i - j)解释一下dp[i-1][j]表示在i-1个数中已经有j个小于号这时有(j1)个位置可以插入i且不增加小于号数量dp[i-1][j-1]表示在i-1个数中只有j-1个小于号这时有(i - j)个位置可以插入i并恰好增加一个小于号因为原来有(i-2)-(j-1)个大于号加上末尾的位置一共是i-j个位置。边界条件dp[1][0] 1因为只有一个数时没有小于号。所有dp[i][j]在j0或ji-1时都是0。#include bits/stdc.h using namespace std; const int MOD 2017; int dp[1005][1005]; int main() { int n, k; cin n k; dp[1][0] 1; for (int i 2; i n; i) { for (int j 0; j i; j) { dp[i][j] (dp[i-1][j] * (j 1)) % MOD; if (j 0) { dp[i][j] (dp[i][j] dp[i-1][j-1] * (i - j)) % MOD; } } } cout dp[n][k] endl; return 0; }我在这里踩过的坑忘记对结果取模。题目明确说了答案对2017取模但我第一次写的时候只在最后输出时取模中间过程没有取模导致大数溢出。实际笔试中所有中间运算都要取模尤其是涉及乘法的地方。另外要注意dp数组的维度要开到n1j的循环范围是0到i-1不能超过i-1否则会访问到未定义的状态。这道题是整套真题中最有“含金量”的一道它考察的是对动态规划状态转移的深入理解。很多同学背模板会做“背包”、“LIS”但一旦换一个场景就不知道怎么定义状态了。插空法是一种非常通用的DP状态设计思路值得多找几道类似的题练手比如“排列的逆序对数量”。3.6 进制转换——细节决定成败的经典题最后一题是一个数字进制转换的问题给定一个十进制数M以及需要转换的进制数N2≤N≤16将十进制数M转换成N进制数。如果M是负数负号要保留。当N大于10时应该使用大写字母A-F来表示10-15。听起来非常简单就是一个“除N取余法”但它在真实笔试中通过率却不高。原因有两个一是很多人没处理负数的情况二是很多人没处理M等于0的情况还有一个容易踩坑的点是转换结果要逆序输出——你算出来的第一个余数其实是转换结果的最低位。#include bits/stdc.h using namespace std; int main() { int m, n; cin m n; if (m 0) { cout 0 endl; return 0; } bool negative false; if (m 0) { negative true; m -m; } string chars 0123456789ABCDEF; string ans; while (m 0) { ans chars[m % n]; m / n; } if (negative) ans -; reverse(ans.begin(), ans.end()); cout ans endl; return 0; }为什么处理0的情况如此重要很多程序在m0的时候直接进入while循环循环一次都不执行最终输出的ans是空字符串判题直接判错。这种边界情况在笔试里特别常见出题人就是故意留这种坑来拉开区分度。还有一个细节C里对负数取余结果的正负号跟随被除数比如-7 % 2等于-1而不是1。所以一定要先把负数转成正数再处理最后再把负号加回去。这是很多初学者容易忽视的点。这道题看上去太简单了甚至算不上“编程题”但它恰恰是笔试中“基石型”题目——用来检验你的基本功扎不扎实。很多时候大厂笔试题的压轴难题都是从这些基础题演变过来的改条件、加限制、结合其他数据结构。能把这些基础题做到满分解笔试就已经成功了一大半。4. 实战经验笔试过程中的策略与技巧4.1 时间分配45-60-15法则百度的这套题我建议的时间分配策略是“45-60-15”。前45分钟集中做前3道相对简单的题买帽子、度度熊回家、寻找三角形每道题控制在15分钟以内中间60分钟攻坚后两道核心题有趣的排序、不等式数列最后15分钟用来做进制转换题和全面检查。这套节奏的依据是简单题先做能快速建立信心同时保证基础分到手。有趣的排序和不]等式数列虽然难度高但只要思路对了代码量其实很少——它们真正花时间的是思考而不是写码。进制转换放到最后是因为它实现简单但边界条件多在心态紧张的时候最容易犯低级错误留到最后专门处理反而能提高准确率。实战中我的经验是如果一道题想了10分钟还完全没有头绪先标记好跳过继续做后面的题。笔试时间宝贵不要在一道题上死磕。等你把会做的题都做完了再回头来冷静思考跳过的题目往往会有新的思路。4.2 输入输出处理笔试翻车的第一大原因很多人刷题的时候习惯用牛客网、LeetCode这种平台函数接口已经帮你处理好了输入输出。但百度的笔试用的是自己的OJ系统要求你写出完整的程序——包括读入、处理和输出。每年都有大量同学在这个环节失分特别可惜。我见过的最惨痛的教训题目的输入是“第一行一个整数N第二行N个整数”但有人写的读入代码是读一行之后用空格分割。如果测试数据里有多余的换行或者末尾有多余的空格就会读入错误。虽然题目数据通常不会那么刁钻但养成逐行读取、按需处理的习惯总是不错的读取完所有输入后检查一下有没有漏读 输入数据量不确定时用 while(cin x) 循环读取 输出结果时注意换行符——很多OJ要求每行输出末尾带换行4.3 模运算与极端数据笔试中隐藏的分数杀手回看这套题不等式数列要求对2017取模进制转换要求处理负数买帽子要求处理重复元素这些其实都是在考察你对“边界情况”的敏感度。从我的经验来看最终笔试成绩拉开差距的往往就是这些边界情况——思路正确但边界没处理好的代码跟思路正确且边界完善的代码在分数上可能是天壤之别。有几个固定的检查清单每次提交前都要过一遍数据范围N取最大值时int够不够要不要用long long特殊输入0、负数、空数组、全相同元素、最大/最小值输出格式小数位数、换行、保留负号数组越界循环边界是 n还是 nn-1和n1会不会越界4.4 从这套题里能带走的东西这套题的考点其实有很强的代表性我梳理了一张“考点能力映射表”方便你对照查漏补缺题目底层能力刷题建议买帽子边界意识、去重思维把简单题做对是为了给难题留时间度度熊回家枚举优化、暴力转高效能用公式推导的变化量就不要真的去模拟寻找三角形浮点数精度、公式选型遇到几何题优先用叉积有趣的排序反向思维、从“不动”入手遇到最少操作先想哪些可以不动不等式数列DP状态设计、插空法排列计数类DP是常考内容进制转换边界处理、基础功把0、负数、进制转换每个细节都处理好如果你做完这套真题发现自己有五道以上能独立做出来说明你的基础功底已经很扎实了。如果只能做出来两三道也不必气馁——笔试本来就是查漏补缺的过程把不会的题弄懂并总结成自己的解题模板就是最大的收获。5. 高频踩坑与提升建议5.1 这套题里最容易踩的坑整理一下我在批改别人代码和自己在实际做题中遇到的典型问题坑点一简单题用了复杂写法反而容易出错买帽子那题有人非要用堆、用平衡树来维护前三小的不同值逻辑绕了好几层最后反而在边界条件上翻车。实际上一个排序加去重就能解决。在笔试场景下可读性和正确性优先于炫技——只要复杂度达标最朴素的写法永远是最不容易出错的。坑点二浮点数比较踩坑寻找三角形这题有人判断三点是否共线时直接用面积是否等于0来判断。但因为浮点数误差计算结果可能是0.0000000001而不是0。更稳妥的做法是设定一个极小值epsilon比如1e-9面积小于epsilon才认为共线。不过这道题因为取的是最大面积所以即使共线导致个别面积算成极小值也不会影响最大值但养成用epsilon的习惯总没错。坑点三状态初始化遗漏不等式数列的DP初始化只设了dp[1][0] 1。如果你把dp[i][0]的递推也写出来会发现它等于dp[i-1][0] * 1一直等于1——这是对的因为1到i的排列中完全升序的情况只有一种小于号数量为0。所有的DP题第一步先把边界条件和初始化状态想清楚否则后面全乱套。5.2 刷这套题的正确姿势如果你准备用这套题来模拟笔试我建议按照“全真模拟”的节奏来设置一个倒计时两个小时内不中断地完成全部6道题最好在一个安静的环境里模拟真实笔试的紧张感。做完之后无论成绩如何拿出一整天时间来复盘——不是看看答案就完事而是把每一道题从头到尾重新推导一遍总结出“我为什么没想到这个解法”“这个解法为什么是对的”。针对薄弱环节再做专项训练如果不等式数列没做出来就去刷一刷排列计数类的DP如果有趣的排序没思路就多练习需要反向思维的题目。这套题只是一个起点用它找出自己的问题然后对症下药才是它的最大价值。5.3 笔试前一周的备战清单根据我多年的经验笔试前一周最有效的冲刺方式是前三天每天做一套真题不限公司只看思路不看答案模拟真实笔试第四天重点复习DP和贪心的常见模型背包、LIS、LCS、区间DP、状态机DP第五天复习字符串处理、进制转换、大数问题这些“小知识点”最后两天调整作息不再做新题只看错题和笔记保持手感和心态这里特别想强调的一点是笔试前的晚上一定要早睡。我知道很多同学喜欢在考前突击到凌晨但笔试考的更多是思维敏捷度和稳定性休息不足的大脑很难发挥出真实水平。这套题里有不少需要细心处理的边界条件精神不好就特别容易漏。5.4 这套题对“面经”的启示这套真题做下来你能明显感觉到百度的工程师文化——题目不追求偏难怪但特别看重思维的灵活性和基础功的扎实程度。这也和百度的面试风格一脉相承面试官喜欢在你做完题目之后追问“为什么这么做”“还有没有更好的方案”其实就是看你是否真正理解了思路而不是背下了代码。所以备考的时候不要只刷题不思考。每一道题做完之后多问自己几个“为什么”为什么这个状态转移是对的有没有其他解法两个解法的复杂度差在哪里这样做一轮下来你的收获会远远超过单纯刷题三倍的量。6. 写在最后回头看百度2017春招这套题它就像一套精心设计的能力检验题从简单的排序去重到需要反复推敲的DP状态设计6道题难度梯度合理每道题都对基本功提出了明确要求。我见过不少基础不错的同学在这套题上栽跟头也见过基础中等的同学利用它高效复盘最终在正式笔试中超常发挥。差别不在于天赋而在于是否认真对待每一道题背后的思维训练。如果你正在准备大厂笔试不妨在刷了大量LeetCode之后试着用这套真题做一次“摸底考试”。把时间卡到两个小时独立完成所有题目然后逐题复盘找出自己的薄弱点再针对性地强化训练。这个过程比盲目刷新题有意义得多。我个人在这套题上最大的收获并不是学会了某一道题的解法而是体会到了一种备考节奏先刷真题摸底再针对弱点补强最后带着清晰的认知上考场。这套方法帮助我带过的很多同学拿到了心仪的大厂Offer。希望这篇拆解对你同样有帮助也希望你今年春招顺利上岸。最后分享一个小技巧每次笔试前把你这套题中做错的题的代码重新手写一遍不要看任何参考资料——手写代码这件事能帮你发现很多你以为会了但其实不会的细节。