十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

360春招笔试编程题详解:从栈到区间涂色的算法实战

360春招笔试编程题详解:从栈到区间涂色的算法实战 360公司2018年春招笔试编程题合集又到了一年一度的春招季我翻到自己早年间整理的笔试题库时看到2018年360春招那批题还是觉得值得拿出来重新聊聊。那年我印象很深360的在线笔试用的是赛码网题目整体不算偏难怪比某些上来就是红黑树变形的厂子温和得多但温和不等于好拿分。选择题覆盖了操作系统、网络、数据结构和几道C/Java语法题中规中矩真正拉差距的是后面的编程题一共三道考察点集中在模拟、栈、区间处理这些在职级P4/P5日常开发里特别常见的能力。也正是因为这套题“不算难”它筛掉的恰恰是那些只会背题、一上机就手忙脚乱的人。这篇东西不是单纯把题目和答案抄一遍而是把我自己当年在考场上的思路、后来复盘时补全的多种解法以及赛码网这个在线评测系统在输入输出上的坑都整理出来。不管你是正在准备校招还是工作两年想跳槽回头补算法基础这套题都值得认真过一遍。题目本身是2018年的但考察的底层思维在2025年的今天依然适用尤其适合刚起步刷题的人做“第一套真题”。1. 这套笔试题的构成与考察逻辑1.1 选择题部分藏着哪些基础考点先说选择题。360春招的笔试时间一般是一个半小时选择题大概二十道左右单题分值不高但错多了也很伤。从当年的题目分布看几个大头非常固定。第一个大头是操作系统。进程和线程的区别、死锁产生的四个必要条件、虚拟内存和分页机制这些几乎是必考。有一道题我到现在还记得问的是“在分页存储管理中页面大小对缺页率的影响”这题表面上考页面大小实际上考的是局部性原理和页表项数量的权衡。页面太小页表项太多TLB命中率下降页面太大内部碎片增多局部性又会被浪费。这种题没有标准最优解而是让你理解“工程是取舍”这件事OS这块尤其明显。第二个大头是计算机网络。TCP三次握手和四次挥手几乎是每套题都会出现的360那年考得更细一点问的是TIME_WAIT状态为什么需要等待2MSL。很多人背过答案说“为了保证最后ACK能到达”这其实只说对了一半。另一半是如果主动关闭方直接进入CLOSED状态而最后一次ACK丢失被动关闭方重发FIN时主动方已经不在监听状态了会直接回一个RST导致被动方异常。所以2MSL的本质是给网络中可能残留的报文段一个“过期时间”确保旧连接的数据包不会串到新连接里。这个逻辑想通了比死记硬背强得多。第三个大头是数据结构和算法。栈和队列的区别、二叉树的遍历、排序算法的稳定性和时间复杂度这些属于送分题。但360喜欢在排序上做文章比如问“下列哪种排序算法在平均情况下时间复杂度最低但最坏情况下可能会退化”其实就是在说快速排序。当时不少人直接选快排忽视了“最坏情况下退化为O(n²)”这个前提条件。题目本身不难难的是仔细读题。第四个大头是语言基础。那年Java和C都考了Java那边是HashMap的原理、String不可变性、异常处理结构C这边是虚函数、指针和引用的区别、构造和析构顺序。如果你是主攻其他语言的建议考前突击一下这两门语言的基础概念因为大厂笔试经常混着考你不一定用它们写代码但得能看懂选择题。1.2 三道编程题的难度梯度和风格选择是开胃菜编程题才是正菜。360 2018年春招的编程题是三道难度上是明显的梯度分布。第一道是纯模拟题给三条边判断三角形类型属于典型的“签到题”但签到题也会有人丢分因为边界条件和浮点数精度没处理好。第二道是字符串括号匹配考察栈的应用比第一道稍微多绕一个弯需要你想到“栈顶元素和当前字符的对应关系”而不是简单计数。第三道上了一个台阶是一个区间涂色问题表面上看着是模拟但数据范围一大直接暴力就超时了需要想到倒序处理或者线段树这类优化手段。这个梯度设计其实挺标准的第一道题用来淘汰完全不上机练过的人第二道题淘汰只会背API不会组合数据结构的人第三道题淘汰“只能写对但写不快”的人。三道题全AC的人通常都不是靠运气而是靠平时积累的套路多。有意思的是这套题的风格和后来几年的笔试风格是一脉相承的不追求偏题怪题更看重“你在有限时间内能不能把一个明确需求落地成可运行的代码”。这本质上就是日常工作里最常见的状态——需求不复杂但时间紧、边界条件多、还要跑得够快。2. 精讲第一题三角形判断里的送分陷阱2.1 题目回忆与考点定位这道题的原题大意是输入三条边的长度a、b、c先判断能否构成三角形。如果不能构成三角形输出“Not a triangle”如果能构成再分类输出是“Equilateral”等边、“Isosceles”等腰还是“Scalene”普通。数据范围我记得是1到10的9次方反正不是小数量级所以如果你用int去接输入某些语言里会溢出。这是一个非常隐蔽的坑你想着“边长嘛能有多大”结果测试数据直接上一组大数int存不下算出来就是个负数后面比较逻辑全乱套。所以不管题目给没给范围凡是涉及长度、数量、金额这类可能很大的数值优先考虑long这个习惯会帮你避开很多测试点。考点其实非常集中第一是条件判断的完备性第二是对数学定义的准确理解第三是输入输出的规范性。这道题因为逻辑简单反而能看出一个人写代码的基本功干不干净。2.2 完整解法和代码实现我的思路分成两步。第一步判断能否构成三角形。传统教科书说的是“任意两边之和大于第三边”你当然可以写三个条件做与运算。但更稳妥的方式是先排序从小到大排好之后只需要判断“较短的两条边之和是否大于最长边”。这两者在数学上完全等价但代码写起来少两个条件也更不容易漏。设排序后三条边是x y z那么if (x y z) { // 不能构成三角形 }第二步在能构成三角形的基础上分类等边x y y z等腰x y || y z注意因为已经排过序所以只需要检查相邻是否相等普通以上都不是这里有个很多人踩过的坑等边三角形同时也是等腰三角形。题目如果要求“按等边优先输出”那就必须先判断等边再判断等腰不能反过来。否则等边三角形会被错误归类为等腰。完整Java代码如下import java.util.Arrays; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); while (sc.hasNext()) { long[] edges new long[3]; for (int i 0; i 3; i) { edges[i] sc.nextLong(); } Arrays.sort(edges); long x edges[0], y edges[1], z edges[2]; if (x y z) { System.out.println(Not a triangle); continue; } if (x y y z) { System.out.println(Equilateral); } else if (x y || y z) { System.out.println(Isosceles); } else { System.out.println(Scalene); } } sc.close(); } }while (sc.hasNext())是为了应对赛码网的多组输入模式。如果你只处理一组数据笔试系统通常会给你“格式错误”而不是“答案错误”因为输出会被判定为不完整。这一点我在后文会专门展开。2.3 哪些细节容易被测试点卡住我当年做这道题的时候第一次提交没有AC原因有两个。第一个是数据类型。那时候我年轻直接用int接的。题目说边长范围是1到10的9次方int在Java里最大也就21亿多两个10的9次方相加是20亿勉强在范围内。但如果测试数据不是10的9次方而是更大或者题面压根没写上限int就直接爆了。后来我学乖了凡是输入数据表示“个数、长度、金额”这类可以参与加减运算的量一律用long不加思索。第二个是输出格式。这道题要求输出“Not a triangle”、“Equilateral”、“Isosceles”、“Scalene”大小写和空格必须和题目完全一致。很多人逻辑写对了结果输出成“not a triangle”或者少了一个词照样被判错。在线评测系统对输出格式要求非常严格多一个空格都可能Wrong Answer更别说单词拼错了。这个问题在笔试里太常见根本原因是本地调试时自己不会去抠这些但系统会抠。还有一个值得提的细节判断等腰的时候很多人会写成x y || x z || y z。这个写法没错但如果你先排过序其实只需要检查x y || y z。这不是性能问题而是代码简洁性的问题。在笔试这种高压环境下简洁的代码意味着更少的思考负担、更少的出错可能。3. 精讲第二题括号匹配不只是一道栈题3.1 原题要你做什么实际在考什么第二道题的原题大意是输入一个只包含()和[]括号的字符串判断这个字符串里的括号是否合法匹配。所谓合法匹配就是每个左括号都有对应类型的右括号并且括号不能交错比如([)]就是非法的。这道题看起来是栈的经典应用但实际考你的不只是“会不会用栈”而是“能不能在紧张状态下保持思维清晰”。因为括号匹配问题有几种不同的变体有些人练过“括号生成”有些人练过“最长有效括号”到了考场上一看字符串里有两种括号反而不知道从哪下手。其实核心思想就一句话用一个栈维护还未匹配的左括号遇到左括号入栈遇到右括号时检查栈顶是否和它对应对应就弹出不对应或栈空就是非法。这道题在当年还是有一定区分度的因为不少人会把题目简化成“数一数左括号和右括号数量是否相等”。如果只考一种括号这个思路勉强能用于部分情况但一旦有(和[两种括号计数法完全失效因为([)]这种串左右数量各相等却是非法的。这就是为什么每次说到栈的经典应用括号匹配都是首选案例——它直接演示了“仅仅计数不够还需要记住顺序”这个关键点。3.2 最直观的栈解法和复杂度分析我给的解法是标准栈解法Java实现import java.util.Scanner; import java.util.Stack; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); while (sc.hasNext()) { String line sc.nextLine().trim(); if (line.length() 0) { continue; } System.out.println(isValid(line) ? Yes : No); } sc.close(); } private static boolean isValid(String s) { StackCharacter stack new Stack(); for (char c : s.toCharArray()) { if (c ( || c [) { stack.push(c); } else if (c )) { if (stack.isEmpty() || stack.pop() ! () { return false; } } else if (c ]) { if (stack.isEmpty() || stack.pop() ! [) { return false; } } } return stack.isEmpty(); } }复杂度很好分析每个字符最多入栈一次、出栈一次时间复杂度O(n)栈深度取决于嵌套层数最坏情况O(n)。空间上栈本身占O(n)空间这个解法不是最优的但足够通过笔试。有一个非常常见的错误是遇到右括号时先stack.pop()再比较而不是先检查栈空。如果栈本来就是空的pop()会抛异常然后整个程序崩溃。虽然在线评测系统一般会返回运行时错误而不是让你一脸懵地卡住但在笔试那种紧张状态下这种低级异常真的会浪费好几分钟。所以我的习惯是判空和比较写在一起像上面代码里那样stack.isEmpty() || stack.pop() ! (利用Java的短路求值栈空时直接返回false不会执行后面的pop()。这个写法简洁又安全。3.3 更省空间的思路只用一个计数器做这道题的时候如果你只遇到了一种括号确实有一个空间O(1)的解法用一个计数器遇到左括号加一右括号减一任何时刻计数器为负则非法最终计数器为0则合法。但这个解法只适用于单种括号。一旦匹配规则变成“左括号必须对应同类型右括号”计数法就无能为力了因为你需要记录括号的类型和顺序。不过如果想优化第二种情况也不是完全没有办法。有一种思路是用“栈内只存左括号类型”的变体比如用字符数组模拟栈而不是用Stack类这样省去了自动装箱和扩容的开销。在笔试场景下这种微优化其实意义不大但如果你在同样的OJ系统里做极限数据测试区别还是能看到的。我的建议是笔试时别纠结这些用最不容易写错的写法先把正确率拿到。真正需要做空间优化的地方是那些你一眼就能看出会MLE的题而不是这种字符串题。3.4 我在考场上忽略的一个边界条件当年做这道题时我写完之后自测的用例都过了但提交后第一个测试点就WA了。后来检查发现是因为我没有处理空行输入。赛码网有的题目输入末尾可能带空行sc.nextLine()会读到一个空字符串我的代码拿空字符串去判断栈空的最终结果确实也是合法按理不该WA。但问题出在输出层我多输出了一行“Yes”和预期输出对照不上于是被判错。这个教训让我形成了两个习惯。第一个凡是读到空行能跳过就跳过不要让它进入业务逻辑。第二个写完代码后手动构造几个“空串”、“单字符”、“全都是右括号”的边界用例先测一遍再提交。很多人觉得边界条件是竞赛选手才需要在意的事情其实校招笔试的测试点里一定有这些它们就是拿来卡“只写主流程”的人的。4. 精讲第三题涂色问题里的区间处理思维4.1 题目描述为什么说它是整套题的分水岭第三道题大概是这样描述的有一排长度为n的木板初始时都是空的我们给它们编号为0到n-1。接下来有m次操作每次操作给出一个区间[l, r]和一个颜色c把区间内的所有木板涂成颜色c。后涂的颜色会覆盖先涂的颜色。问所有操作结束后每个木板的最终颜色是什么。输出从0到n-1每个木板的颜色值。这道题第一眼看就是纯模拟理论上你用两层循环外层遍历m次操作内层遍历区间里的每个木板就能得到答案。但问题是数据范围。如果n最大是10的5次方m也是10的5次方区间长度也可能是10的5次方级别那么最坏情况下总操作数是10的10次方在赛码网那种时间限制下Java代码基本跑不完C也悬。所以这道题的关键不在于你知不知道如何涂色而在于你能否识别出“暴力会超时”这个信号并且想到用区间处理的优化方法。这就是我前面说的它考的不是某个高深算法而是最常用的区间思维。4.2 暴力模拟为什么会超时以及优化的两个方向我们先明确暴力解法的时间复杂度O(m * avgLen)也就是操作次数乘以平均区间长度。当n和m都到10的5次方时这个量级在1秒的时限下是铁定跑不完的。所以必须优化。优化方向有两个我先说比较好想的那个倒序处理。核心思路是最后一次涂的颜色一定不会被覆盖所以我们从后往前处理操作。如果一个木板已经被“最终颜色”决定了那它就不再需要被后续也就是更早的操作覆盖。这样每个木板就被处理一次总复杂度降为O(n m)。具体怎么做先初始化一个数组ans全部为0表示空。从第m-1个操作逆序遍历到第0个操作。对每个操作我们需要知道区间[l, r]中还有哪些木板没被最终确定。如果朴素地去遍历区间里每个元素复杂度并没有降下来。这时可以用并查集的“跳跃”技巧维护一个“下一个未确定位置”的数组每次处理完一个位置就把它的下一个指向i1这样在区间内就能通过路径压缩快速跳到下一个未确定的位置。这里我给出Java实现代码不长但值得反复琢磨public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); int n sc.nextInt(); int m sc.nextInt(); int[] l new int[m]; int[] r new int[m]; int[] c new int[m]; for (int i 0; i m; i) { l[i] sc.nextInt(); r[i] sc.nextInt(); c[i] sc.nextInt(); } int[] ans new int[n]; int[] parent new int[n 1]; // parent[i] 表示第 i 个位置的“下一个待处理位置” for (int i 0; i n; i) { parent[i] i; } // 并查集 find带路径压缩 // 这里把 find 写成方法更清晰 for (int i m - 1; i 0; i--) { int start find(l[i], parent); int end r[i]; while (start end) { ans[start] c[i]; // 确定最终颜色 parent[start] start 1; // 跳过已确定的位置 start find(start, parent); // 跳到下一个未确定的位置 } } StringBuilder sb new StringBuilder(); for (int i 0; i n; i) { if (i 0) { sb.append( ); } sb.append(ans[i]); } System.out.println(sb.toString()); } private static int find(int x, int[] parent) { if (parent[x] ! x) { parent[x] find(parent[x], parent); } return parent[x]; } }关键在find的路径压缩。每次确定一个位置后我们让它的parent指向start 1下次从同一区间扫描时就能直接跳到还没被涂色的位置不会重复遍历。这样每个位置最多被处理一次再加上并查集的接近O(1)的复杂度整体是O(n * α(n) m * α(n))完全可以跑过。第二个优化方向是用线段树的区间赋值。线段树可以在O(log n)时间内完成区间赋值和单点查询整体复杂度是O(m log n)同样能过。但是相对并查集倒序处理来说线段树需要写的代码明显更多在笔试时间紧张的情况下更容易写错。所以如果只要求最终结果我更推荐倒序加并查集因为它思维的“临场负担”更小。只要能想到“逆序处理”这一步代码就只是套模板。4.3 这类题最容易忽略的是“顺序”本身这题我最想强调的是倒序处理不是一个只会在涂色题里用到的技巧它是区间覆盖类问题的通用思路。比如你在一张表格上往某些单元格里填数据后面的操作会覆盖前面的最后只关心最终结果那么永远是从后往前处理最简单。再比如任务调度里某个时间点之后的任务优先级更高那么逆序处理也经常用来简化逻辑。这种“逆序思维”一旦掌握你会发现在不少场景下都能派上用场。另外还有一个细节容易被忽略如果定义的颜色值可能是0那初始化为0再倒序处理时就可能把“没被涂到”的位置和“被涂成颜色0”的位置混淆。这种情况下初始值最好用-1处理完再输出。当年题目里颜色范围是1到1的9次方所以用0初始化没事但换一个变体题就要小心这个坑。5. 那些年赛码网笔试环境的隐藏坑5.1 多组输入与输出格式的细节说完三道题我得专门聊一聊赛码网这个在线评测系统因为很多人在题库里刷题刷得很顺一上赛码网就懵。这套题的笔试就是在赛码网完成的那会儿它的界面和牛客网不太一样输入输出格式的坑也更隐蔽。赛码网的编程题通常有两种输入模式。第一种是“单组输入”就是题目只给一组测试数据你读一次就行。第二种是“多组输入”题目会连续给多组测试数据要求你一组一组地处理直到输入结束。2018年360这套题里的第一题和第二题都是多组输入的所以代码里要用while (sc.hasNext())或者while (cin ...)这种循环结构而不是只读一次就结束。很多人在牛客网上习惯了单组输入的写法到了赛码网的题目里没写循环结果只处理了第一组数据。评测系统会给多组输入你的程序处理完第一组就退出了系统判定你输出不完整直接判WA。这个错误不涉及算法纯粹是对平台不熟悉非常可惜。5.2 递归深度和栈溢出问题还有一类常见问题是递归深度。如果面试官给的题目适合用递归解比如二叉树遍历、DFS求连通块你要特别注意Java和C默认的栈空间。Java的默认线程栈一般只有1MB递归10万层的时候基本会栈溢出。2018年这套题里虽然没有明显的递归题但我在其他笔试里吃过这个亏所以顺带说一下。解决方案有两个。第一个是把递归改成显式栈的迭代写法能用循环解决的尽量不用递归。第二个是在本地测试时加大栈空间Java可以在运行时加-Xss参数比如-Xss512m。但必须要说明笔试平台一般不允许你修改JVM参数所以你不能依赖这个方法最好在写题阶段就想清楚递归层数会不会太深如果会立刻换迭代写法。5.3 提交前必须检查的三件事结合这套题的经验我整理了一个提交前的检查清单每次笔试都按照这个顺序过一遍能减少很多无谓的丢分。第一确认读入用的是while (hasNext())循环而不是只读一次。这个前面说了是针对赛码网多组输入模式的。第二检查输出格式。重点看大小写、空格、换行。如果题目要求每个结果一行那就一行一个如果要求空格分隔那就别用换行。输出格式错了再正确的算法也拿不到分。第三确认没有多余的调试输出。很多人本地写代码会加一些System.out.println(debug: ...)来辅助排查提交前忘记删最后这些输出会被当成答案的一部分。特别是赛码网这种严格比对输出的系统一行多余的debug输出就会导致全错。5.4 我一次真实的上机事故我印象特别深的一次事故是在某次模拟笔试中第一道题是类似三角形判断的题我写完逻辑后在自己电脑上测了几组数据都通过了于是直接提交。结果WA了十几次怎么都想不通。最后发现我的输入代码写的是int n sc.nextInt(); for (int i 0; i n; i) { // 处理每组数据 }也就是说我假设题目第一行会告诉我一共有多少组数据。但赛码网的题面根本没有给这个总数它是直接连续输入多组数据直到EOF。我的代码只处理了第一组然后程序正常退出剩下的测试数据根本没人处理。这要是在真实的笔试环境里我整道题基本就白给了。从那以后我就养成了一个习惯不管题目描述里有没有提到多组输入我都尽量用while (hasNext())/while ((line reader.readLine()) ! null)这种读取方式。如果题目确实是单组输入这种写法也不会出错顶多多检查一次退出条件。用最通用的写法去适配最复杂的情况这是一个性价比很高的习惯。6. 从2018年这套题看今天的校招笔试6.1 为什么一套七年前的题还值得刷有人可能会问2018年的题都过去这么多年了还有必要翻出来做吗我的观点是校招笔试的变化速度远没有很多人想象的那么快。表面上现在很多厂开始用AI辅助编码工具出题题型可能更贴近工程场景但底层的数据结构、算法复杂度、边界处理能力七八年来没有本质变化。360这套题里的三角形判断、括号匹配、区间涂色至今仍然是笔试中出现频率很高的题型。你把这些题吃透了遇到它们的变体思路迁移非常自然。而且这套题的难度梯度特别适合做“真题入门”。它不像某些厂的题那样一来就是Hard难度让人直接劝退也不像纯签到题那样让人练不出东西。它的三道题刚好覆盖了“模拟、栈、区间优化”三个层次每一道都能让你学到点东西做完之后有正反馈。对第一次接触校招笔试的人来说这是非常理想的第一套真题。6.2 如果让我重新准备一次我会怎么练如果我现在回到校招季重新准备我会按照这样的节奏来第一周把所有基础数据结构过一遍包括数组、链表、栈、队列、哈希表、二叉树。重点是每种结构的使用场景比如“什么时候用栈而不是数组”、“什么时候用哈希表而不是遍历”。这一轮不需要做太多题每类精选三五道就够了。第二周到第三周集中刷模拟题和字符串题。模拟题锻炼的是把自然语言描述的规则转换成代码的能力字符串题则把重点放到边界情况和特殊输入上。三角形判断和括号匹配就是这两个专题的典型题目。这个阶段的目标是“简单题不丢分中档题写得快”。第四周开始做区间处理和动态规划。区间处理的核心是线段树、树状数组、并查集与差分动态规划先掌握线性DP和背包问题。涂色题就是区间处理里非常典型的案例它既锻炼了暴力思想又让你见识了优化的必要性和手法。现阶段目标是“中高档题能写出一种正确解法哪怕不是最优也能拿下部分得分点”。第五周做真题套题。集中做三五家目标公司的往年笔试专门训练时间分配和心态。如果你的目标公司里包含360那这套2018年的题可以放在最前面做因为它的风格比较典型不会出现太多偏题。6.3 笔试之外的提醒心态和答题顺序最后说点关于答题顺序的个人体会。笔试的时候我建议先把编程题全部读一遍判断难度。然后从最简单的那道开始写不要按题目顺序写。先拿满容易拿的分再去啃难题这是收益最大化的策略。2018年这套题的三道题本来就是按难度递增排列的所以顺着做问题不大。但有些笔试题会把简单题和难题穿插排列或者题干描述得很长但实现很简单。如果不先通读一遍你可能在第一道难题上卡了四十分钟后面的简单题都没时间写。会做但没时间写是所有笔试场景里最亏的丢分方式。还有一点如果你在赛码网上提交后发现WA不要慌先看错误类型。如果是WA大概率是某个边界条件没处理如果是超时优先考虑算法复杂度问题这个时候可以迅速用暴力解法先拿部分分别死磕在AC上。校招笔试通常不是“全对才能过”很多时候是通过率卡线的你多拿下一个测试点的分都有可能改变结果。
返回列表