
先把话说在前面这套百度2017春招笔试真题编程题集合我在去年帮学弟做校招模拟的时候又翻出来完整刷了一遍刷完最大的感受是——题不算难但每一道都长在面试官的审美点上。它没有堆砌冷门算法也没有故意用超长题干吓人而是把字符串、排序、动态规划、枚举这几块基础功揉进几个带点小故事的题面里让你在“有点意思”和“能写出来”之间反复横跳。这篇文章我尽量按“先看懂考什么、再逐题拆思路、最后给可跑的代码和避坑清单”这个顺序来写。不管你是正在准备校招的应届生还是想跳槽但几年没刷题的社招选手只要把这几道题吃透再去看其他大厂的笔试题你会明显觉得套路眼熟。1. 先搞清楚这套题考什么2017百度春招笔试复盘1.1 四道题两小时考的是基本功那年的春招笔试基本是线上OJ的形式给一个网址限时两小时四道编程题按通过测试点的比例给分。我给很多同学模拟过这个环节这两年大家普遍反馈是“题面读得懂但一动手就卡住”。这套题就是很典型的例子它不靠难题劝退你而是靠“你以为会做”的题筛选你。我当时把四道题按类型分了一下字符串处理、排序去重、动态规划、枚举优化。没有图论、没有线段树、没有复杂数据结构最重的一道也就是线性DP。这说明出题人很清楚校招笔试阶段要的不是竞赛选手而是基础扎实、能快速把思路变成代码的人。还有一点值得注意题面里大量出现了“度度熊”这个角色。这种拟人化包装不是随便加的它会让整张卷子读起来不那么冷冰冰但从做题角度讲你第一件事应该是把故事剥掉直接看输入输出格式。从这套题开始我就养成了一个习惯——先看样例再看题面效率能高一截。1.2 考点分布和出题风格代码量小坑点隐蔽我统计了一下这套题的代码量用C写核心逻辑大概每题30到60行用Python更短。但它绝对不是“送分题”坑都埋在细节里而且是笔试现场最容易翻车的那种细节。具体来说这套题的坑点集中在三块一是去重比如买帽子那题价格相等算一顶还是两顶题目不说明你就得从生活常识推断二是取模不等式数列的结果要模2017我当时第一次做就是因为忘记每一步都取模导致中间结果溢出三是边界值子串长度为1、数组只有一个元素、跳过点的位置在端点附近这些都是测试点特别喜欢埋雷的位置。出题风格上我可以直接用一句话形容题目都来自真实业务场景的抽象。比如“度度熊回家”其实就是路径优化的简化版“度度熊找子串”在搜索引擎里对应字符串匹配的雏形。这个风格在百度之星和校招题里一脉相承后面几年的题也延续了这种“生活化包装经典算法内核”的路子。1.3 都2025年了为什么还值得刷可能有人会问2017年的题到现在还有什么参考价值我明确说在算法题这个领域经典的永远是经典的。数据结构会迭代框架会升级但“滑动窗口去重”“线性DP递推”“枚举最优解”这些底层的思维模式到今天依然是笔试面试的核心考察点。我自己在带新人做模拟面试时也经常从这套题里抽一两道作为开胃菜。原因很简单它不像LeetCode那样有大量“你刷过就会”的题解惯性也不像ACM题那样劝退它处在一个非常适合用来检验真实编程能力的区间。你如果能不看题解在45分钟内把四道题全部写出并通过样例那你的笔试基础基本就过关了。2. 经典题目逐题拆解从读题到破题2.1 度度熊找子串一眼滑窗但要小心去重这道题的原题描述大致是给定一个只含小写字母的字符串和一个整数k问有多少个长度为k的连续子串满足里面的k个字符互不相同。拿到题第一反应肯定是滑动窗口。窗口大小固定为k维护窗口内每个字符出现的次数再维护一个“当前窗口内不同字符的个数”。右指针向右移动加入一个新字符当窗口长度超过k时左指针向右移动删掉一个字符。每次窗口长度等于k且不同字符数也等于k时答案加一。这里有个隐含条件需要仔细想题目问的是“子串个数”还是“不同子串个数”如果只求位置不同那滑动窗口直接计数就行。如果要求内容不同那就麻烦了因为两个位置不同但内容相同的子串只能算一次这时候就需要把每个满足条件的子串哈希后丢进set去重。我当时是按“位置不同”来处理的但从严谨角度建议你拿到题先看清楚“连续子串”和“互不相同”这两个限定词再决定要不要额外去重。这道题的时间复杂度是O(n)空间复杂度是O(1)因为字符集只有26个小写字母。如果k大于26直接返回0因为字符串里不可能有超过26个互不相同的连续字符。这个边界条件是我后来反复提醒自己才记住的。2.2 买帽子排序去重第三便宜可能不存在“度度熊想去商场买一顶帽子商场里有n顶帽子有些帽子的价格相同。度度熊想买第三便宜的帽子问第三便宜的价格是多少。”这题的名气在这套题里仅次于不等式数列原因不是它难而是它精准地踩中了很多人的惯性思维。我看到题的第一反应是排序然后输出第三个数。但仔细一想价格相同的情况下“第三便宜”到底指的是所有价格排序后的第三个位置还是去重之后的第三小价格从实际语义出发如果两顶帽子都是50元那“最便宜”“第二便宜”“第三便宜”应该算作不同位置的帽子还是算作同一个价位题目最终的标准做法是先去重再取第三小。这其实也符合常识价格档次才叫第几便宜而不是物理位置上的第几顶。所以解法就是把所有价格放进set去重再排序如果去重后数量小于3输出-1否则输出第三个数。复杂度O(n log n)数据范围通常很小直接一个sort搞定。这题最大的价值不是教你怎么排序而是提醒你笔试里的每句话都可能是考点尤其是“相同”“不同”“至少”“恰好”这些词。2.3 不等式数列那个DP递推到底怎么来的这题是整套题里最有含金量的一道也是我当时卡得最久的一道。题目大意是把1到n这n个数字排成一排相邻两个数字之间有n-1个符号每个符号要么是“”要么是“”。问有多少种排列方式使得恰好有k个“”其余都是“”结果对2017取模。第一眼看这题如果n很小可以直接全排列暴力但n能到1000暴力完全不可行。这时候就要往动态规划想。我一开始的思路是考虑数字的排列关系但卡在如何保证排列合法。后来把状态定义为dp[i][j]表示用1到i这i个数字排成一排形成j个“”的方案数。关键在于“插入第i个数时怎么转移”。很多人会误以为第i个数就是最大的那个所以它插入任意位置只会影响它左右两个相邻关系。实际上考虑已有i-1个数字的合法排列现在要插入数字i它比所有已有数字都大。如果插在序列最左端会新增一个“”小于号数量不变如果插在序列最右端会新增一个“”小于号数量加1如果插在内部某个位置比如原本是“x y”插入i后变成“x i y”一个“”变成了一个“”加一个“”小于号数量不变如果原本是“x y”插入i后变成“x i y”一个“”变成了一个“”加一个“”小于号数量加1。这个分析推下来就得到转移方程dp[i][j] dp[i-1][j] * (j 1)插入后小于号数量不变。位置包括最左端1个以及原本j个“”所在的内部位置共j1个。dp[i][j] dp[i-1][j-1] * (i - j)插入后小于号数量加1。位置包括最右端1个以及原本的“”内部位置共(i-1)-(j-1) i-j个。最后dp[n][k]就是答案。初始化dp[1][0]1。这个递推的巧妙之处在于数字本身的大小关系通过“插入最大值”这个操作天然得到了保证不需要真的生成排列。我第一次做的时候没有把“插入位置数”和“小于号数量”的关系想透后来手推了n3、n4的所有情况才彻底明白。建议大家也动手枚举一下n4、k0到3的所有方案这个推导一旦自己完成以后再遇到类似的排列计数题就稳了。2.4 度度熊回家别一上来就前缀和枚举更快这道题给的是数轴上的一组坐标点第一个点是起点最后一个点是终点中间有若干个途经点。度度熊想跳过其中一个途经点问跳过之后从起点走到终点的最短总路程是多少。坐标点不保证按顺序排列。我见过不少人拿到这题先想前缀和、后缀和因为“从起点经过所有点到终点”确实可以预处理累加距离。但其实这道题的数据范围很小直接枚举跳过的那个点计算跳过前后的总距离差取最优解就行。完全没必要上复杂的优化。具体做法是先算出来不跳过任何点时的总距离total。然后枚举每一个可以跳过的中间点i下标从1到n-2不包含起点和终点计算跳过它带来的距离变化。原本经过i的路径是a[i-1]到a[i]再从a[i]到a[i1]跳过之后直接从a[i-1]到a[i1]所以总距离变化量就是abs(a[i]-a[i-1]) abs(a[i1]-a[i]) - abs(a[i1]-a[i-1])。取所有变化量中的最大值用total减去它就是答案。这个做法的复杂度是O(n)枚举一遍就完事。很多同学为什么在这题上卡住因为他们下意识觉得“最短路径”一定要用图算法但实际上这题根本没有岔路就是一条链上的距离计算。这提醒我们笔试里看到“最短”先看图的形态链就直接算树再考虑DFS图才上最短路径算法。3. 核心实现细节与完整代码3.1 度度熊找子串的Python实现我优先用Python复现这套题不是因为C不好而是Python在这类“逻辑为主”的题目上可读性更高方便面试时跟面试官讲思路。以下是我整理过一版带注释的滑窗代码。def count_substrings(s: str, k: int) - int: if k 26: return 0 n len(s) if n k: return 0 cnt [0] * 26 diverse 0 # 当前窗口内不同字符的个数 ans 0 for right in range(n): idx ord(s[right]) - ord(a) if cnt[idx] 0: diverse 1 cnt[idx] 1 left right - k if left 0: old ord(s[left]) - ord(a) cnt[old] - 1 if cnt[old] 0: diverse - 1 if right k - 1 and diverse k: ans 1 return ans print(count_substrings(abcabc, 3)) # 期望输出 4 print(count_substrings(aaaa, 2)) # 期望输出 0这里的核心是我们不需要在每次移动窗口时重新统计一遍字符只需要维护diverse这个变量。右指针每加一个字符如果这个字符从0变成1diverse加1左指针每移出一个字符如果这个字符从1变成0diverse减1。判断条件里窗口长度恰好为k时如果diversek就说明这k个字符彼此都不同答案加1。我给几个测试用例方便你自测输入sababab, k2期望输出0因为任意两个相邻字符都相同输入sabcd, k3期望输出2abc和bcd两个子串满足。写这类题的时候一定要把“窗口长度不满k”的前期阶段处理对很多人就是在right从0到k-2时错误地累加了答案。3.2 不等式数列的滚动数组实现DP部分如果直接开一个二维数组n最大1000会占1MB左右其实也能过。但为了养成好习惯我建议直接用滚动数组优化成一维因为转移只依赖上一行。MOD 2017 def count_inequality(n: int, k: int) - int: dp [0] * (n 1) dp[0] 1 # i1, j0 for i in range(2, n 1): new_dp [0] * (n 1) for j in range(0, i): # 小于号数量最多 i-1 if dp[j] ! 0: # 小于号数量不变 new_dp[j] (new_dp[j] dp[j] * (j 1)) % MOD # 小于号数量加1 if j 1 n: new_dp[j 1] (new_dp[j 1] dp[j] * (i - j)) % MOD dp new_dp return dp[k] print(count_inequality(4, 2)) # 期望输出 5可手推验证 print(count_inequality(4, 3)) # 期望输出 1只能是 1234这里有两个细节必须注意。第一个是取模每一步乘法加法都要取模不能只到最后再取因为中间结果可能非常大Python虽然大整数不会溢出但速度会明显变慢C则是直接溢出。第二个是j的取值范围前i个数最多有i-1个小于号所以第二层循环到i不用遍历n。关于k的输入题目一般会保证0 k n-1但你自己写的时候可以加一个判断如果k越界直接输出0。这个判断在某些测试平台上是能帮你挽回一个测试点的。3.3 度度熊回家的枚举实现这题实现起来是最快的可能10行代码就够。def min_distance_after_skip(a): n len(a) if n 2: return 0 total 0 for i in range(1, n): total abs(a[i] - a[i - 1]) max_save 0 for i in range(1, n - 1): original abs(a[i] - a[i - 1]) abs(a[i 1] - a[i]) skipped abs(a[i 1] - a[i - 1]) save original - skipped if save max_save: max_save save return total - max_save print(min_distance_after_skip([1, 3, 2, 5])) # 期望输出 4我解释一下这个代码的易错点坐标不保证有序所以所有距离计算都必须用abs不能默认后面的点一定比前面的大。另外跳过点的时候只能跳过中间的途经点起点和终点不能跳所以枚举范围是range(1, n-1)。还有一个边界情况如果n3只有一个途经点那答案就等于直接从点0到点2的距离代码里总距离减去保存值正好能算出这个结果。如果n2就根本没有途经点可跳直接返回0。这个判断很多人会漏。3.4 三组容易写错的测试用例代码写完之后一定要在提交前用自己构造的边界用例跑一遍。我整理了三组在笔试现场最容易暴露问题的用例你可以在本地验证第一组针对字符串题sa, k1此时窗口内只有一个字符diverse1应该输出1。很多人会在判断里写rightk-1时没考虑k1的情况导致ans加了两次或漏加。第二组针对不等式数列n1, k0一个数字没有任何相邻符号方案数应该是1。如果循环从i2开始dp初始化是1直接返回dp[0]1没问题。但如果初始化写错成dp[0]0这组数据就会挂。第三组针对回家题a[1,1,1]所有点重合总距离是0跳不跳都一样输出0。有些人在计算max_save时初始化为一个很大的数最后total - max_save变成了负数就是这里出了岔子。边界测试不是浪费时间它能帮你把那些“看起来对但实际跑不对”的细节提前干掉。4. 现场笔试常见问题与排查技巧4.1 输入输出的隐藏坑线上笔试的输入输出格式简直是校招翻车第一重灾区。这套题在不同平台上流传的版本有的要求循环读入多组测试数据有的只读一组有的输出要求换行有的要求行末没有多余空格。我见过太多代码逻辑全对但卡在读入格式上最后整题0分的情况。我的建议是拿到题先看输入描述如果写的是“输入包含多组测试数据”那就要用while循环读直到EOF如果只写了“输入为一行”那就老老实实只读一行。千万不要把LeetCode上那种已经封装好的函数签名带到牛客或赛码这种需要自己处理输入输出的平台上这是两个完全不同的模式。输出方面一般整型答案直接print就行但如果题目要求保留小数位比如面积题一定要用format函数精确控制小数位不能直接print浮点数否则可能在格式化上产生误差。4.2 暴力超时不是算法问题是估算问题很多同学一看到n1000就扔一个O(n^3)的暴力上去然后毫无悬念地TLE。其实只要动手估算一下1000的三次方是10亿任何语言在2秒时限内都跑不完而1000的平方是100万这就很稳了。所以在写暴力之前先花30秒算一下复杂度。我有一个自己常用的经验值2秒时限内O(n)能跑1e8量级O(n log n)能跑1e7量级O(n^2)能跑1e5到1e6量级O(n^3)最多只能处理n300左右。数据范围一给你就知道该往哪个方向想。比如不等式数列这种n1000的题如果第一时间想到的是全排列那复杂度是O(n!)不用测也知道跑不动必须想到DP。4.3 语言选择与编译器的差异这套题是2017年的当时主流是C但2025年的今天用Python的求职者越来越多。笔试平台对Python的时间限制通常会放宽一些但也不会无限制放宽。我的建议是如果两道题是同一复杂度选你最有把握的语言如果你对C模板和STL不熟不要为了“显得专业”强行用CPython在这套题上的表现足够了。另外要特别注意某些平台的Python环境是2.7还是3.x可能存在差异。input()、print()、//运算这些行为都不同。有条件的话提前去目标平台做一套模拟题确认环境版本和标准输入输出方式这个准备工作非常值得。4.4 时间分配先拿分再优化最后说一个老生常谈但总有人不听的时间分配问题。四道题分值大概率不是平均的前面简单的题可能占30分后面难的占20分这时候如果你的策略是死磕一道难题最后前面三道只留半小时那总分大概率不理想。我的实战策略是先用10到15分钟把四道题全部看一遍给每道题标一个“思路清晰程度”的分数。思路最清晰的那道题先写写完立刻提交拿到基础分。然后按从易到难的顺序推进。如果某道题卡了20分钟还没有任何突破果断跳过把后面能拿的分先拿到手。最后如果有剩余时间再回头补那道卡住的题。这个策略的精髓在于在线笔试的评判标准不是“你做出几道完美题”而是“你的总分比别人高”。有时候一道题的暴力解法能拿一半的分那就先交暴力不要追求一步到位。我个人这几年带人刷题最大的体会是笔试层面能把经典模型快速识别出来比会写冷门高级算法重要得多。这套2017百度春招题就是很好的试金石它覆盖的滑动窗口、去重思维、DP递推、枚举优化几乎是大厂笔试出场率最高的四个模块。如果你把这里的每一道题都自己推一遍、写一遍、测一遍再拿LeetCode上同类型的题做巩固应对其他公司的校招笔试会从容很多。最后再分享一个小技巧刷这套题的时候别光看题解准备一本草稿纸把不等式数列的DP转移自己从n1推到n4把每个插入位置都画出来。这个过程看起来慢但它能帮你真正建立“递推是怎么来的”这种感觉。有了这种手感你后面再遇到排列类DP、方案计数类DP都能很快找到状态定义的方向。