行业资讯
📅 2026/8/29 18:10:40
360春招笔试编程题解析:核心考点与拿分策略
2018年春季360公司春招笔试的编程题合集在各个求职论坛上传得挺火。作为一个当年亲身参加过360笔试、后来也参与过校招面试环节的过来人我对这套题目的印象很深——它不像某些大厂笔试那样一上来就是竞赛级难题而是把大量精力放在了基础数据结构、字符串处理和边界条件上。这篇文章我就把360春招笔试编程题的核心拆解一遍说说每道题背后到底在考什么以及我当年踩过的那些坑。如果你是准备投递360或者其他互联网公司后端、算法岗的应届生这篇内容会帮你理清准备方向。即使你不面360这套题目的出题风格也很有代表性不追求偏题怪题而是考察你在有限时间内能不能把基础题写出干净、正确、高效的代码。这恰恰是校招笔试最真实的筛选逻辑。1. 360春招笔试考什么整体结构与考察意图1.1 题型构成与时间分配2018年360春招笔试的编程题部分整体上分为两个大的方向一类是纯算法题需要手写完整解法另一类是偏工程实现的选择题或简答题考察语言基础和操作系统、网络等计算机基础知识。编程题的数量一般在3到4道左右时间大约是两个小时也就是说每道题平均只有30到40分钟。这个时间压力是很真实的。我当年拿到试卷的第一反应是先扫一遍所有题目而不是从第一题开始闷头做。为什么因为不同题目的难度差异可能很大如果第一题卡住了后面能做出来的题也没时间写。我的建议是先花两分钟通读全部题目按“能不能马上想到思路”给题目排个序把最有把握的题先拿到分再回头啃难题。这套策略看着简单但我在考场上帮了大忙。1.2 笔试真正的考察目标很多人以为笔试就是在考“会不会做题”其实不完全是。360的笔试题目有一个很明显的特点它更看重你的代码是否完整、是否能处理边界情况、是否有良好的代码习惯。举个例子同样是实现一个字符串处理函数能想到处理空字符串、超长输入、特殊字符的考生和只写了一个主流程就交卷的考生在面试官眼里的差距是巨大的。另外笔试中出现的算法题大部分是《剑指Offer》和LeetCode中等难度题目的变体。这意味着你不需要去刷那些极难的数据结构题但一定要把常见算法模板练熟。我当时在准备阶段就把“手写快排”“手写二分”“手写链表反转”这类基础操作练到了肌肉记忆的程度后来在笔试中确实派上了用场。1.3 网上热词里的另一面顺便说一句最近网上关于360安全卫士纯净版、360壁纸卸载、360浏览器怎么彻底卸载的讨论很多。作为技术人员我对这些工具类问题不太感冒但这里提醒一句在准备笔试的过程中与其花时间去折腾怎么卸载某个软件不如把精力放在算法题上。360这家公司虽然以安全软件被大众熟知但它的笔试题目其实非常正统更看重你的计算机基础功。这部分我们接下来细说。2. 高频题型一字符串处理与模拟类题目2.1 字符串类题目的通用套路字符串处理是360笔试的高频考点几乎每年都会出现。这类题目的特点是对代码实现的精细度要求很高常见考察点包括括号匹配、字符串解码、子串查找与替换、正则表达式简化版本等。我做字符串题总结了一套通用思路第一优先考虑用栈来处理嵌套结构因为字符串的嵌套匹配本质上是栈的天然场景第二如果题目要求对字符串进行多次变换要特别注意每次操作后索引是否失效第三边界条件必须单独处理比如字符串为空、长度为1、末尾是分隔符等情况。很多同学在笔试时字符串题容易超时不是思路不对而是用了一些O(n²)的暴力解法。比如“判断一个字符串是否由另一个字符串循环移位得到”这类题如果你去拼接字符串再用contains判断Java里就是O(n*m)的复杂度但原题考察的其实是字符串匹配的优化思路。这种“看似简单、实则要优化”的题正是360笔试喜欢出的类型。2.2 经典题“字符串解码”的详细解析当年360春招出现过一道字符串解码的题目我记得很清楚大意是输入一个形如“3[abc]2[de]”的压缩字符串要求输出解压后的完整字符串“abcabcabcdede”。这道题在LeetCode上也有原题394. Decode String但考场上没有编译器提示全靠自己写对边界难度会高不少。解题思路有两种。一种是递归法解析到数字后递归解析后续字符串直到遇到与当前层匹配的右括号。核心代码如下Python版def decode_string(s: str) - str: def dfs(i: int): res [] num 0 while i len(s): if s[i].isdigit(): num num * 10 int(s[i]) elif s[i] [: i, inner dfs(i 1) res.append(inner * num) num 0 elif s[i] ]: return i, .join(res) else: res.append(s[i]) i 1 return i, .join(res) _, result dfs(0) return result另一种是栈解法用两个栈分别保存“数字”和“当前层拼接结果”。遍历时遇到左括号把当前结果入栈遇到右括号出栈并重复拼接。推荐栈解法因为不需要递归的额外栈空间也更容易扩展到更复杂的语法。这道题我在考场上第一版就漏了“嵌套数字可能是多位数”的情况比如“12[a]”应该解析成“aaaaaaaaaaaa”而不是“2[a]”。这个坑希望大家注意笔试中多写几个测试用例自测一下是值得的。2.3 模拟类题目的边界处理心得模拟类题目在360笔试中也占一定比例比如“按照规则模拟一个进程调度”“模拟一个库存系统”。这类题本身算法难度不大但特别容易在“题意理解偏差”上失分。我的经验是把题目中的规则逐条翻译成代码注释一条规则对应一个函数或一个分支比如“如果库存不足则拒绝本次请求”“如果时间冲突则跳过该任务”这样写出来的代码结构清晰也方便自我检查。另外特别注意模拟题经常会考察“同一时刻发生多件事”的处理顺序要先决定优先级再动手写代码否则改起来非常痛苦。3. 高频题型二数据结构设计与LRU缓存3.1 为什么笔试钟情LRU360笔试中多次出现“设计一个LRU缓存”这道题。它之所以被各家公司轮流考察是因为它一道题就能考察多个核心能力双向链表操作的熟练度、哈希表的运用、O(1)时间复杂度的设计思路以及对“缓存淘汰策略”背后的业务理解。很多同学能背出LRU最近最少使用的概念但一到手写就卡住了。卡住的核心原因是不知道为什么要用“哈希表 双向链表”的组合。哈希表负责O(1)查找双向链表负责O(1)插入和删除。如果只用数组每次访问后调整顺序需要O(n)如果只用链表查找需要O(n)。只有两个结构配合才能保证get和put都是O(1)复杂度。3.2 手写LRU的实现与复杂度分析我直接给出Java版本的标准实现这是我在笔试中常用的模板import java.util.HashMap; class LRUCache { class Node { int key, value; Node prev, next; Node(int key, int value) { this.key key; this.value value; } } private final int capacity; private final HashMapInteger, Node map new HashMap(); private final Node head new Node(-1, -1); // 哨兵节点 private final Node tail new Node(-1, -1); public LRUCache(int capacity) { this.capacity capacity; head.next tail; tail.prev head; } public int get(int key) { if (!map.containsKey(key)) return -1; Node node map.get(key); moveToTail(node); return node.value; } public void put(int key, int value) { if (map.containsKey(key)) { Node node map.get(key); node.value value; moveToTail(node); } else { if (map.size() capacity) { Node removed head.next; removeNode(removed); map.remove(removed.key); } Node newNode new Node(key, value); map.put(key, newNode); addToTail(newNode); } } private void removeNode(Node node) { node.prev.next node.next; node.next.prev node.prev; } private void addToTail(Node node) { node.prev tail.prev; node.next tail; tail.prev.next node; tail.prev node; } private void moveToTail(Node node) { removeNode(node); addToTail(node); } }这里最容易被忽略的是哨兵节点的设计。用head和tail两个哨兵可以省去大量“节点是否为空”的判断这也是实际工程中链表实现的常用技巧。时间复杂度get和put都是O(1)空间复杂度O(capacity)。面试中如果被问到“为什么能O(1)”你就要把这个哈希表和双向链表的分工讲清楚。我当年在笔试后单独被面试官追问过“如果让你不用内置HashMap你还能实现O(1)查找吗”这个扩展问题考的是你有没有真正理解哈希表的原理。3.3 缓存容量选择的考量虽然笔试中LRU的capacity是输入参数但实际系统设计时容量选择大有文章。比如你给数据库查询做缓存容量太小命中率低容量太大占用内存。通常的做法是根据平均单个缓存项的大小和可用内存来估算比如每个缓存项平均4KB机器可用内存2GB加上系统其他开销那么容量大概可以设为10万量级。笔试不需要你考虑这么细但这个思考过程可以用来应对面试的追问。我当时就补充了一句“如果缓存项大小差距大可以用加权LRU的变体”面试官明显对这轮回答比较满意。4. 高频题型三动态规划与贪心算法4.1 经典题“圈地运动”的几何思考360笔试考过一道有点意思的题叫作“圈地运动”。题目大意是给你一组正整数数组每根木棍的长度已知问从数组开头取连续的前n根木棍最少取多少根才能围成一个闭合多边形。我第一次见这道题时愣了一下因为它披着几何的外衣实际上是一个数学判断加贪心扫描的题。核心是“多边形判定定理”给定n条边能围成多边形的充要条件是“最长边小于其余所有边之和”。换句话说n 2且maxLen sum - maxLen。基于这个判断直接从前向后扫描每次维护前缀和和当前最大值第一个满足条件的位置就是答案。def min_fence_count(lengths): prefix_sum 0 max_len 0 for i, length in enumerate(lengths): prefix_sum length max_len max(max_len, length) if i 2 and max_len prefix_sum - max_len: return i 1 return -1这个解法的复杂度是O(n)空间O(1)。如果你去暴力枚举所有组合那复杂度是O(n³)在n较大时必然超时。这道题告诉我们笔试中的“几何题”往往是幌子真正考的还是数学建模和扫描法的基本功。4.2 动态规划的状态设计思路动态规划是360笔试的绝对主力题型。我记得出现过类似“找零钱最少硬币数”的变体和“最大子数组和”的变体。这类题目的核心不是写代码而是“定义状态”。我总结了一个遇到DP题的思考顺序第一步看题目能否分解成更小的相同问题第二步定义一个数组dp[i]明确dp[i]表示“以i结尾时的最优值”还是“前i个元素的最优值”第三步找状态转移方程用前一个状态表示当前状态第四步确定初始化条件。以“最大子数组和”为例dp[i]表示以第i个元素结尾的连续子数组的最大和则dp[i] max(nums[i], dp[i-1] nums[i])最终答案是max(dp)。笔试时最容易错的是初始化和边界。比如数组为空时返回什么只有一个元素时dp数组能不能直接遍历我自己的习惯是写DP之前先想好“最小规模的例子”比如n1时程序会怎么走。这个习惯帮我避开了大量低级错误。4.3 什么时候用贪心什么时候用DP有些题目看起来既可以用贪心也可以用DP比如“跳跃游戏”这类。360笔试中如果出现这种题我的经验是优先尝试贪心因为贪心代码量更少、不容易出错但如果题目要求“求所有方案中的最优数量”大概率要用DP因为贪心只能求“是否可行”。这里有一个经典区分点如果每一步的选择会影响后面的选择且局部最优不一定导致全局最优那就需要DP如果每一步都可以通过一个简单规则选出当前最优且这个选择不会影响后续判断那就是贪心。考场上判断错了会非常浪费时间所以建议大家考前把两类题目各刷20道形成直觉。5. 高频题型四图论与搜索5.1 最短路径与拓扑排序的实战360笔试中的图论题一般不会太复杂常见的是单源最短路径和拓扑排序。单源最短路径如果图中没有负权边直接用Dijkstra如果节点数少但边数多也可以考虑Floyd。但考场上最保险的其实是“从每个节点出发做BFS”的暴力思路因为笔试题的图通常不大正确性比最优复杂度更重要。拓扑排序考得也很多特别是和“课程安排”“依赖关系”相关的题目。判断有向图是否存在环最常见的解法是Kahn算法基于入度和DFS三色标记法。我建议把Kahn算法背熟因为它还能顺便输出拓扑序列适用面更广。5.2 并查集在连通性问题中的应用并查集是笔试中的“隐藏常客”。很多看起来是图搜索的题其实用并查集能写得更简洁。比如“判断两个节点是否连通”“统计岛屿数量”这类题并查集的代码简洁且不容易错。class UnionFind: def __init__(self, n): self.parent list(range(n)) self.rank [0] * n def find(self, x): if self.parent[x] ! x: self.parent[x] self.find(self.parent[x]) # 路径压缩 return self.parent[x] def union(self, x, y): root_x, root_y self.find(x), self.find(y) if root_x root_y: return if self.rank[root_x] self.rank[root_y]: self.parent[root_x] root_y elif self.rank[root_x] self.rank[root_y]: self.parent[root_y] root_x else: self.parent[root_y] root_x self.rank[root_x] 1这段代码里的路径压缩和按秩合并是性能关键。理论上加入了这两个优化后并查集单次操作的时间复杂度约为反阿克曼函数在现实中可以认为接近O(1)。笔试时不需要跟面试官解释反阿克曼函数但“为什么find里要路径压缩”这个问题要能答上来。6. 实战模拟完整笔试过程中的踩坑记录6.1 时间分配失误的典型场景我当年做360笔试时时间分配出现过一次失误。前两道题我花了大把时间去做最复杂的优化结果第三道题虽然思路很简单但因为剩余时间太少代码写得太急出现了下标越界的低级错误。这个教训很惨痛笔试不是竞赛不求“最优化解”而是求“完整AC”。我的调整策略是每道题先写一个最暴力的版本保证小规模数据能通过然后如果时间充裕再优化。暴力版本往往能帮你理清思路而且在测试用例不大时暴力版本也能拿不少分。千万不要一上来就写最复杂的解法一旦中间断逻辑调试时间就会成倍增加。6.2 编译器与IDE选择360笔试的平台通常支持你自己选择语言和本地IDE。我的建议是用你最熟悉的语言不要为了“显得高级”去用不熟的冷门语言。Java选手一定要把HashMap和LinkedList的API记熟Python选手要注意递归深度如果题目数据量较大手写栈比递归更稳妥。还有一个很多人会忽略的点本地代码能跑通不代表平台能跑通。原因可能是主类名、包名、输入输出格式不对。笔试平台通常要求输出“严格匹配”多一个空格、少一个换行都可能判错。提交前务必检查是否把调试用的System.out.println删掉了是否按题目要求处理了多组输入这些细节我见过太多人丢分。6.3 多测试用例的推导技巧笔试现场其实允许你用“小数据测试法”来验证算法。比如写一段随机数据生成器或者手写几个极端案例包括空输入、单元素输入、全部相同输入、逆序输入、最大数值输入。这比盲目改代码有效得多。我自己的经验是每个算法写完必须验证这三类案例一是规模最小的长度为0或1二是规模大但值分布的如全正、全负、正负交替三是数据边界值如用int的最大值。把这些案例过一遍至少能排查掉八成以上的隐性bug。7. 备战建议与常见问题排查7.1 刷题优先级如果你距离笔试还有一个月我给一个可执行的刷题优先级第一优先级是线性表操作包括链表反转、删除重复元素、合并有序链表第二优先级是字符串问题尤其是LeetCode字符串分类里的中等难度题第三优先级是DP的经典模型包括背包问题、最长公共子序列、最长递增子序列第四优先级才是图论和高级数据结构。这个顺序是根据360以及同类公司笔试出题频率排的。先把基础题刷透把代码写得又快又准比刷一百道难题但都是“看了答案才会”要强得多。刷题过程中建议自己写题解不用发出来写给自己看就行。写题解的过程会强迫你想清楚“为什么这么做”而不是“我背了个模板”。7.2 常见问题速查表问题现象可能原因解决方案本地输出正确但平台判WA输出格式不匹配检查空格、换行、大小写数组越界边界条件未处理在循环入口加索引判断递归栈溢出递归深度过大改为显式栈或迭代死循环while条件未更新检查循环内是否有break或变量更新超时算法复杂度过高换用哈希表/前缀和/动态规划优化输出多了调试日志忘了删除System.out.println提交前全局搜索print说到底笔试只有一件事在压力下写出正确代码。与其焦虑题海无边不如把历年真题反复做三遍每一遍都按考试标准要求自己。当你拿到一套题能稳定地在一小时内AC两道中等题、半小时内AC一道简单题的时候通过笔试就不成问题了。我在实际备赛过程中最深的感受是题库会变但考察的能力不会变。360这套题合集里暴露出的要点——字符串处理、LRU、DP状态设计、图论基础——放在今天依然不过时。你把这些基础能力练扎实了不管是去360还是其他公司笔试这条路都会好走很多。最后再分享一个小技巧每次做完题花十分钟把题目的核心考点写在一张索引卡上考前翻一遍比重新刷一遍题效率高得多。