☰
360春招笔试复盘:算法题型与解题思路全解析(含避坑指南)
2026/10/11 19:21:18 网站建设 项目流程

又到一年春招季,不少同学应该已经在牛客、力扣上刷了一圈题,准备投360的春招。我是去年参加的第二批笔试,当时做完之后整理了不少复盘笔记,一直没来得及发出来。今天就把当时的原题考点、我的解题思路、还有踩过的坑一次性说清楚,给接下来要上场的同学做个参考。这篇文章不涉及具体原题泄露,只讲题型分布、解法思路和备考策略,都是我基于考后回忆和同类题型整理的,大家放心看。

这次360的春招编程题整体风格偏实际应用,不像某些厂爱出那种纯脑洞的数学题,更看重基本功和边界处理能力。题型主要集中在线段树、动态规划、字符串处理、双指针这几个方向,题目难度梯度设计得比较明显,第一题基本送分,最后一题才会真正拉开差距。

1. 春招笔试的整体定位与考点分析

1.1 360春招编程题的难度与风格定位

笔试一共两批,我参加的是第二批。整体感受是:代码量不大,但思路拐弯比较多,尤其是中等题喜欢在经典算法上做变种,比如把贪心藏在区间合并里,把DP藏在一维数据里。

对比牛客网上能搜到的历年360真题,2023年的批次延续了几个固定风格:

  • 第一题通常是模拟或简单字符串,送分题,考察读题仔细程度和基础代码速度
  • 第二题开始进入正题,常考前缀和、双指针、滑动窗口这类优化思路
  • 第三题是中高难度,线段树、树状数组、状态压缩DP出现频率较高
  • 压轴题往往是综合题,可能在拓扑排序、并查集、DP加贪心的组合上做文章

还有一个容易被忽略的点:360的笔试环境用的是牛客网的在线OJ,支持C++、Java、Python等主流语言。这里要提醒一句,能用C++或Java的尽量别用Python。不是Python不行,而是牛客的判题机对Python的递归深度和大量输入处理的性能压得比较狠,同样的算法复杂度,C++一遍过,Python可能卡在超时边缘。

1.2 常考能力模型与备考优先级

从题目反推考察的能力模型,360比较看重下面这几项:

  1. 数组处理能力——不是简单的遍历,而是在一次遍历中完成统计、计算、更新多个状态。比如前缀和配合哈希表,就是高频套路。
  2. 数据结构的熟练度——线段树和树状数组几乎成了360中等以上题目的标配。不是说让你背板子,而是得理解单点修改、区间查询背后的更新逻辑。
  3. 状态转移的建模能力——动态规划题通常不会给你明显的“选或不选”,而是会包装成区间覆盖、任务分配、路径规划的样子。

针对这个能力模型,我建议备考优先级这样排:双指针和滑动窗口 > 前缀和/差分 > 常见DP模型(背包、LIS、区间DP) > 线段树/树状数组 > 图论基础(拓扑、最短路、并查集)。贪心算法不用专项准备,前提是你把排序和数据结构弄熟了,贪心策略通常就是排序加一次遍历的事。

2. 核心题型逐题复盘与解题思路拆解

2.1 字符串与哈希结合的前缀统计题

第二批的第一道编程题,考的是字符串前缀匹配加哈希统计。题面大致意思是:给一组字符串,要求统计有多少个字符串的前缀在另一组字符串中出现过。

这类题属于典型的“看起来能暴力但其实必须优化”的类型。最直接的暴力解法就是两重循环,对每个字符串枚举它的所有前缀,再到目标集合里查。时间复杂度是O(n*m*len),n和m是字符串数量,len是字符串平均长度。如果数据量小没问题,但笔试数据一般会把暴力卡掉。

正确姿势是用哈希集合存目标字符串的所有前缀,然后遍历待匹配字符串判断它的前缀是否在集合里。代码很简单,关键是一次性把某个字符串的所有前缀都生成出来存好,而不是在匹配的时候逐个拼接。

#include <bits/stdc++.h> using namespace std; int main() { int n, m; cin >> n; vector<string> a(n); for (int i = 0; i < n; i++) cin >> a[i]; cin >> m; vector<string> b(m); unordered_set<string> prefix_set; for (int i = 0; i < m; i++) { cin >> b[i]; string cur; for (char c : b[i]) { cur += c; prefix_set.insert(cur); } } int ans = 0; for (string& s : a) { if (prefix_set.count(s)) ans++; } cout << ans << endl; return 0; }

这个解法的时间复杂度是O(总字符数),空间复杂度也是O(总字符数)。要注意的坑是:前缀集合可能非常大,如果字符串总长度是10^6量级,用unordered_set是安全的,但别用set(红黑树),插入和查找都有log n的常数,很容易超时。

2.2 区间合并与贪心结合的覆盖问题

第二批第二题,核心是区间覆盖。给若干个区间,问最少需要保留多少个区间,才能保证每个点至少被一个区间覆盖到。这个题本质上是经典的“最小区间覆盖”贪心模型,只是题干换了一层皮。

贪心策略是固定的:按左端点排序,维护当前覆盖的最右端点,然后每次在左端点不超过当前右端点的所有区间里,选右端点最大的那个。如果找不到下一个区间,说明覆盖断裂,直接返回-1。

这里有个细节很关键:排序是按左端点,但每次贪心选的是右端点最大的区间。很多人卡在这一步,想着按右端点排序也行,其实不行。按左端点排序是为了保证扫描的单调性,每次从起点往后找能接上的区间;按右端点排序会破坏这个顺序,导致覆盖出现空洞。

#include <bits/stdc++.h> using namespace std; int main() { int n, L, R; cin >> n >> L >> R; vector<pair<int,int>> segs(n); for (int i = 0; i < n; i++) cin >> segs[i].first >> segs[i].second; sort(segs.begin(), segs.end()); int cur = L, idx = 0, ans = 0; while (cur < R && idx < n) { int maxr = cur; while (idx < n && segs[idx].first <= cur) { maxr = max(maxr, segs[idx].second); idx++; } if (maxr == cur) break; // 没有能推进的区间 cur = maxr; ans++; } if (cur < R) cout << -1 << endl; else cout << ans << endl; return 0; }

这个题值得深入想一下:为什么贪心在这里是对的?因为在已经覆盖到的范围里,往后延伸时,选择右端点最大的区间一定不会比选择其他区间更差。如果你选了一个较小的右端点,后续还要再选一个区间来补,区间数量只会更多。这就是典型的“局部最优能推出全局最优”的证明思路。

2.3 动态规划:一维状态设计的经典考题

第二批的第三题,是典型的DP题。题面描述得比较绕,但抽象之后本质是:给定一个数组,要求分成若干连续段,每一段的代价定义为段内最大值减最小值,求整体最小总代价。

这个题刚读题的时候容易想复杂,因为它不限分段数量,只看总代价最小。如果你直接想贪心,会发现无从下手,因为每段的最值差受分段方式影响。正确的切入点是先想暴力DP,再想优化。

先定义状态:dp[i]表示前i个元素被分成若干段之后的最小总代价。那么转移方程就是:

dp[i] = min(dp[j] + cost(j+1, i)),其中 j 从 0 到 i-1。

cost(j+1, i) 是子数组从第 j+1 到第 i 个元素的最值差。这个转移是O(n^2)的,n如果在10^3量级勉强能过,但如果n是10^5,必须优化。

优化的思路是单调栈。因为 cost(j+1, i) = max(j+1, i) - min(j+1, i),我们可以分别维护两块贡献:一部分是“以i结尾的某个区间最大值产生的贡献”,另一部分是“最小值产生的贡献”。用单调栈维护区间最大值和最小值的变化,配合线段树维护dp[j]加上当前最值差的最小值。这一步比较高级,笔试现场能写出来的基本是冲满分的那批人。

我建议如果时间紧,先把O(n^2)的暴力DP写出来拿部分分。360的判分机制是按通过用例比例给分的,暴力过掉60%到70%的用例,性价比也很高。笔试不是竞赛,不要求你必须拿满分,拿到足够的分数进面试才是最终目标。

2.4 数据结构的灵活运用:树状数组求逆序数

第二批的压轴题之一是逆序对变种,要求统计每个元素与其左侧比它小的元素组成的逆序对数量之和。经典解法是用归并排序或树状数组,但这里加了点变化:不是数全局逆序对,而是对每个位置统计以它作为较大元素的逆序对数量。

其实用过树状数组的同学都知道,这个套路非常成熟:离散化之后,从左往右扫描,每遇到一个元素就查询树状数组里小于它的元素个数,然后把这个元素插入。核心代码也就二十行,但坑在离散化和数据范围。

#include <bits/stdc++.h> using namespace std; int lowbit(int x) { return x & (-x); } void add(vector<int>& bit, int i, int v) { for (; i < (int)bit.size(); i += lowbit(i)) bit[i] += v; } int query(vector<int>& bit, int i) { int res = 0; for (; i > 0; i -= lowbit(i)) res += bit[i]; return res; } int main() { int n; cin >> n; vector<int> a(n); vector<int> b(n); for (int i = 0; i < n; i++) { cin >> a[i]; b[i] = a[i]; } sort(b.begin(), b.end()); b.erase(unique(b.begin(), b.end()), b.end()); vector<int> bit(b.size() + 1, 0); long long ans = 0; for (int i = 0; i < n; i++) { int id = lower_bound(b.begin(), b.end(), a[i]) - b.begin() + 1; ans += query(bit, id - 1); add(bit, id, 1); } cout << ans << endl; return 0; }

有一个不少新手会犯的错误是忘记离散化,直接把十万甚至百万量级的数值当数组下标用,结果就是越界或者爆内存。遇到大数据范围先离散化,这是树状数组题目的基本动作。

3. 实操过程:笔试现场的做题节奏与试错记录

3.1 时间分配与做题策略

实战的时候,时间管理比刷题量更重要。360笔试总共90分钟,两道到四道编程题不等(批次不同题量不同),第二批实际是四道题。我当时的分配策略是:

  • 前20分钟:通读全部四道题,把每道题的数据范围、时间限制、题面关键词标出来。第一题直接开写,趁脑子还清醒。
  • 20到45分钟:主攻第二题和第三题。边写边验证样例,能跑通样例就立刻提交,先拿到分数。
  • 45到75分钟:做第四题,也就是最难的压轴题。写个暴力版本保底,然后尝试优化。
  • 最后15分钟:检查输入输出格式、边界情况、有没有漏掉多组测试数据的情况。

这个节奏的实际好处是:不会出现最后一题没时间看而空着的情况。因为最后一题即使拿不到全部用例的分数,暴力版本也能覆盖一部分。

3.2 现场翻车的三个真实案例

我复盘自己当时和周围同学的做法,总结了三个典型的翻车场景:

第一个是第一题就被卡住。第一题其实是送分题,但因为前面说的字符串前缀统计,有的同学上来没仔细读题,把“前缀在目标集合中出现过”理解成了“完全匹配”,结果样例过了,提交后只对了一半用例。这种问题纯属读题不仔细,没有别的解释。我的习惯是读完题先把样例在草稿纸上手动推一遍,确认自己的理解跟样例输出完全吻合再动手写代码。

第二个是第二题忘了排序。区间覆盖问题,排序是整个算法的第一步,但有些同学一旦进入写代码状态就急着写主逻辑,把排序漏了。漏排序之后,样例不一定会挂(因为样例里的区间可能恰好有序),但大数据用例一定挂。这个坑的教训是:涉及区间的问题,先问自己“排序了没有”。

第三个是第三题状态定义错误。我前面提到的DP题,有的同学把dp[i]定义成了“前i个元素分成k段的最小代价”,结果发现题目根本不要求分段数量,白白多写一维,时间复杂度直接爆掉。这不是能力问题,是做题习惯问题。写完转移方程之前先画一画状态的含义,确认它只依赖前一个状态或者更早的几个状态,不要盲目加维度。

3.3 从暴力到满分的优化路径

以第三题的DP为例,展示一下我在现场从暴力到优化的实操过程。

第一步,先把O(n^2)的暴力写出来,验证算法的正确性。转移是:

dp[0] = 0 for i in 1..n: mx = -INF mn = INF for j in i-1 down to 0: mx = max(mx, a[j+1]) mn = min(mn, a[j+1]) dp[i] = min(dp[i], dp[j] + mx - mn)

注意内层从i-1倒着往前扫,因为这样mx和mn可以O(1)维护,不需要额外开二维数组存区间的最值。

第二步,观察这个转移的本质:dp[j] + max(j+1,i) - min(j+1,i)。我们能拆成两部分:

  • 左半部分 dp[j] - min(j+1,i),这部分对某个固定i来说,随着j变化,min值呈现单调递减的阶梯状,可以用单调栈维护等价区间
  • 右半部分 max(j+1,i)的贡献,同理用另一个单调栈维护

然后用线段树维护每个j对应的候选值,查询区间最小值,整体复杂度降到O(n log n)。

这个优化需要比较扎实的线段树功底,但它的原理值得反复咀嚼。单调栈在这里的作用是“把连续的一段j合并成同一个值”:当加入的新元素比栈顶元素大时,它会更新一段区间的最大值,我们只需要在线段树上对这段区间做区间更新。这是经典的“单调栈+线段树优化DP”套路,力扣上有好几道题都用到了。

4. 常见问题排查与避坑技巧实录

4.1 输入输出处理的隐藏陷阱

笔试界面用的是牛客OJ,输入方式跟力扣不同,不是给你封装好的函数,而是让你从标准输入读取数据。这块如果处理不好,前面所有努力都白搭。

最常见的问题是多组数据。有些题目会写“输入包含多组测试数据,每组占一行”,这意味你需要用while(cin >> n)这样的循环把每一组都处理掉,而不是只处理一次。如果你只处理了一组数据,那可能第一个用例输出正确,但从第二组开始全部错误。

另一个坑是数组下标越界。特别是当数据范围标注是“10^5”时,别真的开一个大小为10^5的数组备用,应该根据输入n实际分配空间。我在做树状数组那题时,一开始开了一个固定大小的全局数组,结果在本地测试没问题,提交后却被判段错误,就是因为某个测试数据里n比预设值大。稳妥的做法是用vector动态分配。

4.2 时间复杂度预估与超时预防

笔试中经常出现“本地秒出结果,提交却超时”的情况。一个实用的预估方法是:1秒大概能执行3×10^8次简单运算,但如果你用了map、set、vector的拷贝、递归等操作,实际吞吐量要低一个数量级。

我给自己定了一条线:看到n是10^5,就用O(n log n);看到n是10^4,O(n^2)可以承受;看到n是10^6,基本只能O(n)或O(n log n)极简实现。数据范围直接在题面上写着,动笔之前扫一眼,心里就有底了。

关于超时,还有一个容易被忽视的点:循环里别再嵌套字符串拼接。比如那题字符串前缀统计,如果你在循环里用cur += c,这没问题,因为每个字符只操作一次;但如果你写成cur = s.substr(0, k),每生成一个前缀就是O(len)的拷贝,总复杂度直接升到O(n^3),必超时。这也是为什么我用累加方式生成前缀而不截取。

4.3 笔试环境的预备动作

过了笔试不意味着万事大吉,我到面试阶段还被问到了笔试时的一个优化思路。所以建议笔试结束后,立刻把每道题的解法记录到自己的笔记里,特别是当时没做出来的题,回去后花时间补完。360的面试官可能会直接让你讲笔试题的思路,如果你能说出从暴力到优化的完整演进,会是明显的加分项。

再分享一个实用的备考工具组合:牛客网刷题时选“公司真题”分类里的360卷,同时配合力扣的“前缀和”“滑动窗口”“区间DP”标签专题刷。前者用来适应笔试环境,后者用来补算法盲区。我去年秋招前主要就是刷这两个,效果比买课好。

5. 我摔过跤之后总结的刷题方法论

说实话,去年第一次准备360笔试的时候,我走了一段弯路。当时只顾着刷力扣的热题100,心里想着“热门题刷八九百道,笔试肯定没问题”。结果上了考场才发现,力扣热题更多的是单知识点深度考察,而360的笔试比较看重知识点的组合运用。区间覆盖这道题,本身考察了贪心加排序,单看每个知识点都不难,但组合起来就会刷掉一批只会背模板的同学。

后来我调整了复习策略,现在回头看,觉得这套思路值得分享给正在准备春招的朋友:

第一,按题型专项突破,而不是按难度梯度刷题。把同一种套路的不同变种放在一起集中刷,比如连续一周只看滑动窗口的题,把“固定窗口”、“非固定窗口”、“带数据结构维护的窗口”全过一遍。这样在考场上看到题干,你会第一时间识别出题人想考什么套路,而不是站在一个知识点门口反复徘徊。

第二,每道题都要做复杂度分析。这不是让你在草稿纸上精确推导,而是养成“看一眼数据范围就锁定算法复杂度的直觉”。连5分钟都花不到,但能让你在大方向上不跑偏。

第三,一定要模拟真实考场环境。至少提前一周,把所有练习都放到牛客的在线编辑器里做,关闭本地IDE的自动补全和语法提示。说实话,习惯了IDE的自动补全之后,突然切换到OJ的裸编辑器,写代码速度会下降至少三成。提前在OJ环境里练手感,可以有效避免考场上因为“写不惯”而浪费时间。

第四,做完题后一定要复盘。复盘不只是把题解看懂,而是要把这道题跟以前做过的题做类比,抽象出共同的模式。比如前面说到的“单调栈+线段树优化DP”,如果你做过“修剪草坪”那道题,就会发现它们底层是同一个套路。抽象提炼出来的模式,才是你真正掌握的东西。

这些话看起来像是老生常谈,但笔试场上真正能稳定执行的并不多。我第二次做类似的题目时,因为有了复盘形成的模板,十分钟就写出了正确答案。这种手感不是靠临时抱佛脚能速成的,一定是从日常刷题中沉淀下来的。

6. 针对不同基础人群的备考建议

说一下不同水平阶段的针对性打法。如果你还有大约两周时间,现在才开始准备,那么心态和策略比盲目刷题重要得多。

基础偏薄的同学(算法题目前还处于靠暴力过样例阶段)

先把“拿基础分”作为核心目标。这个阶段请把重心放在第一、第二题的送分题级别上:数组操作、字符串处理、简单模拟、基础双指针。刷题时不要怕简单,能五分钟内完整AC一道简单题,比苦思冥想一道难题三小时有价值得多。

具体操作建议:每天固定做十道简单题加两道中等题,简单题练熟练度,中等题练思维拓展。遇到做不出来的题,直接看题解,看懂之后合上题解自己重写一遍。两道中等题里面,只要有一道能独立AC,就已经很了不起了。别贪多,这个阶段的目标是看见题目就有思路,写代码不卡壳。

基础尚可的同学(算法入门已过,经典题型掌握七成)

这个阶段的目标从“会做”转变成“做得快、做得稳”。建议每天做四道中等难度题加一道困难题,重点刷360常考的区间覆盖、前缀和、DP优化、树状数组这四个方向。每道题限时25分钟,到时间没做出来就标记一下,看题解然后第二天重做。

这个阶段特别建议做的事是:把你的解题过程录屏幕或者写成文字。不用发出去,自己复盘就好。你会惊讶地发现很多“我看懂了但写不出来”的题,其实卡在中间某一步的代码实现上,比如区间更新时边界写错。定位出自己的薄弱环节,比多刷十道题还有用。

基础扎实的同学(难题已过关,竞赛经验丰富)

这个阶段大概率不是怕题不会做,而是怕阴沟里翻船。建议把重心放在细节和稳定性上,特别是边界条件、特殊用例(空数组、单元素、全相等、极大极小值)、数据类型溢出这三大类。笔试翻车往往不是栽在难题上,而是栽在简单题的边界条件上。

此外,如果有余力,建议关注一下360的产品线和技术栈,了解他们可能在什么业务场景下出题。比如360做安全、搜索、浏览器等业务,那么字符串匹配、日志分析、用户行为序列这类问题就有可能出现。提前了解业务背景,能在考场上更快理解题干的现实含义。

最后再说一句:笔试不是终点,它就是一次普通的在线考试。发挥失常了还有下一批,不用太有心理负担。真正拉开差距的不是某一次考试的成绩,而是你是不是每次都从考试里学到了一点点东西,然后在下一次用上。祝各位好运,有问题欢迎在评论区交流。

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

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

立即咨询