360春招编程题解析:四道经典算法题与解题思路

360春招编程题解析:四道经典算法题与解题思路 1. 写在前面这份题单到底考什么1.1 一份“回忆版”题单的含金量我在整理2023年360春招编程题第一批的时候先说实话网上流传的版本基本都是考生考完回忆出来的不是官方原题个别测试样例可能有出入但题型分布、考察范围和难度梯度是真实可信的。我翻了好几份复盘帖把重复出现的题目和变形整理到一起再加上自己的解题思路写成了这篇长文。为什么这件事值得单独拿出来写因为360的笔试在互联网公司里属于“风格比较稳定”的那一类——编程题占比高、题目不偏门、重基础数据结构与算法但特别喜欢在细节上埋坑。你要是只刷剑指offer不亲手写代码很可能在字符串处理、边界条件这类地方翻车。尤其现在春招节奏快笔试不过连面试机会都没有所以每一分都得抠。这篇内容适合谁来读准备投360暑期实习或校招的同学打算进安全、IoT、搜索/浏览器相关技术岗的求职者以及所有想系统检验自己基础算法功底的应届生。我不会带你刷完所有题但会把第一批题目里最典型的四道题掰开揉碎讲清楚每道题都给完整可运行的代码、复杂度分析和易错点。你照着练一遍再遇到同类题心里就有底了。1.2 360笔试的出题风格与考察侧重点360的业务线很多——安全卫士、浏览器、搜索、IoT摄像头、智能硬件等。这直接反映在笔试出题上不考高深的ACM竞赛题也不考偏门算法重点集中在字符串处理、数组与滑动窗口、动态规划、贪心模拟这几大块。第一批编程题基本都是这个套路难度大致在LeetCode中等偏易到中等之间但题量不少时间压力真实存在。我整理了这批题目的考察矩阵你可以对照自测题目类型核心考点出现频率推荐掌握程度字符串解压/编码栈、递归、字符串拼接高必须熟练最长无重复子串双指针、哈希表高必须熟练任务调度贪心、优先队列中重点掌握环形数组最大子段和动态规划、环形转换中重点掌握笔试环境一般用牛客网或赛码网支持Python、Java、C等主流语言。我建议你用Python刷因为写起来快、字符串处理方便笔试场景下“能快速AC”比“语言性能极致”更重要。下面四道题我会用Python给出完整解法思路讲清楚方便你迁移到自己熟悉的语言。2. 题目一字符串解压栈的经典应用2.1 题目描述与样例360第一批笔试里有这样一道题输入一个压缩后的字符串规则是“数字[子串]”表示子串重复数字次支持嵌套。比如3[a2[b]]展开后就是abbabbabb。要求输出解压后的完整字符串。这类题目变形很多有的加了大写字母代表变量替换有的括号类型更多但核心思路完全一致。题目本身不难难的是写出能一次通过的代码尤其是面对多层嵌套时很容易在出入栈的环节出bug。笔试常见输入输出格式是这样输入 3[a2[b]] 输出 abbabbabb字符串中只包含小写字母、数字和方括号数字表示重复次数保证输入合法。这个“保证合法”很关键意味着你不需要额外处理括号不匹配、数字溢出之类的异常场景可以专心写核心逻辑。2.2 解题思路为什么用栈而不是递归看到嵌套结构第一反应一般是递归或者用栈模拟。两种都能解但我推荐栈原因是笔试环境下栈的代码更直观、更好调试而且不用担心递归深度和函数调用开销。虽然题目没有明确说字符串最长多长但笔试系统对栈空间有限制递归嵌套过深可能直接栈溢出用迭代实现的栈则完全没这个问题。核心思路是维护两个量当前累积的字符串cur_str以及当前待累积的数字num。从左到右扫描字符遇到数字累加到num上注意数字可能不止一位比如12[a]。遇到[说明要把当前状态入栈保存把cur_str和num打包存进栈然后重置这两个变量开始处理方括号内的子串。遇到]从栈里弹出上一层的字符串和重复次数把当前层的结果按倍数拼接后接在上一层的字符串后面。遇到字母直接追加到cur_str。这个过程你可以理解为“现场工作台”和“仓库”的关系。[意味着“先把手上没做完的半成品放仓库开始新任务”]意味着“新任务完成从仓库取回半成品把新结果装上去”。每一对括号正好对应一次入库和出库顺序不会乱。2.3 Python代码实现def decode_string(s: str) - str: stack [] num 0 cur_str for ch in s: if ch.isdigit(): num num * 10 int(ch) elif ch [: stack.append((cur_str, num)) cur_str num 0 elif ch ]: prev_str, repeat stack.pop() cur_str prev_str cur_str * repeat else: cur_str ch return cur_str if __name__ __main__: s input().strip() print(decode_string(s))这里有个小技巧入栈时保存的cur_str是进入这一层之前已经拼好的字符串而不是当前层的。换句话说每次遇到[你都在保存“这一段括号之前的成果”然后清空工作台去处理括号里的新内容。这样出栈时把括号内的成果乘以倍数再拼接在之前的成果后面顺序就对了。2.4 容易踩的坑这道题我见过太多人栽在同一个地方数字可能是多位数。比如12[a]如果你只写num int(ch)而不做累乘那结果会变成1[a2[a]]的解压结果完全不对。正确的做法是num num * 10 int(ch)每扫描到一个数字就把之前的数字往左挪一位再加上当前位。另一个坑是出栈后的拼接顺序。很多人写成cur_str cur_str * repeat prev_str结果字符串整体反转了。顺序很重要上一层的字符串在左当前层重复后的结果在右。原因是栈是后进先出你最后处理的括号在字符串中往往更靠右拼接时当然应该放在右侧。实测下来这题最优时间复杂度是O(n)n是解压后字符串的长度空间复杂度O(n)。笔试评测一般会构造一些极端嵌套比如100[abc]、2[3[a]4[b]]这种只要数字累乘和拼接顺序没问题都能稳定通过。3. 题目二最长无重复字符子串双指针与哈希表的配合3.1 题目描述与样例第二批里出现频率极高的一道题给定一个字符串找出其中不含有重复字符的最长子串的长度。输入输出大概长这样输入 abcabcbb 输出 3解释最长无重复子串是abc长度为3。如果输入是bbbbb答案就是1因为最长无重复子串是单个字符b。如果输入是pwwkew答案是3对应wke或kew。这道题是LeetCode第3题的原题也是各大厂笔试的“常青树”。360爱考它是因为它既能考察双指针的滑动窗口思想又能考察哈希表的灵活运用而且代码量不大、现场调试方便非常适合作为编程题出现。3.2 思路拆解滑动窗口的滑动时机最朴素的做法是枚举所有子串逐个判断是否有重复字符时间复杂度O(n²)甚至O(n³)笔试必超时。正确的打开方式是滑动窗口。维护两个指针left和right分别指向当前窗口的左右边界窗口内的字符保证互不重复。right每次向右扩展一格把新字符纳入窗口。如果新字符之前已经出现在窗口内说明窗口需要收缩这时就把left移动到上一次出现该字符的位置加1确保窗口内重新变得干净。这里的关键优化是用哈希表记录每个字符最近一次出现的位置而不是用集合判断“是否出现过”。因为集合只能告诉你“出现过没有”不能告诉你“上一次在哪”而你恰恰需要知道位置才能快速移动left。这就像你在一本书里划重点光知道“这个词出现过”没用你得知道“上次出现在第几页”才能确定该从哪一页开始重新读。3.3 Python代码实现def length_of_longest_substring(s: str) - int: last_pos {} left 0 ans 0 for right, ch in enumerate(s): # 如果ch上一次出现的位置在窗口内 left需要移动左边界 if ch in last_pos and last_pos[ch] left: left last_pos[ch] 1 last_pos[ch] right ans max(ans, right - left 1) return ans if __name__ __main__: s input().strip() print(length_of_longest_substring(s))注意判断条件是last_pos[ch] left不是ch in last_pos。因为哈希表里记录的是历史所有位置如果某个字符上一次出现的位置已经跑到窗口左边外面去了说明当前窗口里并没有这个字符不需要移动left。这个细节筛掉了不少人写的时候要留意。3.4 复杂度分析与边界情况时间复杂度O(n)每个字符最多被访问两次一次被right进入窗口一次被left移出窗口空间复杂度O(m)m是字符集大小一般是128或256的常数级别。边界情况我在笔试时都测过空字符串循环不执行返回0正确。全相同字符如aaaa每遇到一个新字符其实是同一个字符满足last_pos[ch] leftleft不断跳到上一个位置的右边窗口长度始终为1答案1正确。字符串长度1直接返回1正确。包含空格、标点、中文哈希表用字符做键天然支持任意字符集不需要额外处理。这题还有一个加强版如果要求返回子串本身而不是长度只需在更新ans时同步记录left和right的位置最后切片返回即可。笔试如果问的是子串内容记得这么处理。4. 题目三任务调度问题贪心策略与冷却时间4.1 题目描述与样例这是一道典型的贪心模拟题360笔试真题里也出现过同类题给定一个用大写字母表示的任务列表每个任务执行需要1个单位时间相同任务之间必须间隔至少n个单位时间冷却时间问执行完所有任务最少需要多少时间。输入 tasks [A,A,A,B,B,B] n 2 输出 8一种最优执行序列是A - B - 冷却 - A - B - 冷却 - A - B共8个单位时间。如果n 0那答案就是任务总数6因为相同任务不需要冷却。这道题考察的是你对贪心策略的理解。任务调度在真实世界中有大量的影子——CPU进程调度、服务器请求限流、广告频控本质上都是“相同类型事件之间需要最小间隔”的问题。360做安全软件和IoT设备这类系统调度逻辑平时肯定没少写笔试考它完全在情理之中。4.2 思路拆解为什么用数学公式而不是模拟模拟法也能解维护每个任务的剩余次数和下一个可用时间每次选一个可执行的任务执行不断循环直到所有任务完成。这个思路直接但代码量偏大而且需要处理优先队列、时间推进等逻辑笔试现场容易写乱。更优雅的做法是数学公式。核心观察是出现次数最多的任务决定了整个时间线的下界。假设出现最多的任务出现了max_freq次那么至少要留出(max_freq - 1) * (n 1) 1个位置才能把这些任务全部放下且满足冷却时间。如果有多个任务出现次数相同且都等于max_freq则还需要在这些位置末尾额外补上max_count - 1个位置。用例子理解假设A出现3次B出现3次n2。可以先把框架搭成A B 冷却 A B 冷却 A B。第一行是A第二行是B因为B和A一样多所以A下面的坑正好被B填满余下类推。如果只有A出现3次其他任务都只出现1次那框架就是A 冷却 冷却 A 冷却 冷却 A剩下的单次任务可以填进冷却空位不影响总时间。公式为结果 max(任务总数, (max_freq - 1) * (n 1) max_count)最后要和任务总数取较大值。因为如果冷却时间很短、任务种类很多此时所有任务加起来都不需要额外冷却答案就是任务总数。4.3 Python代码实现from collections import Counter def least_interval(tasks, n: int) - int: cnt Counter(tasks) max_freq max(cnt.values()) max_count sum(1 for v in cnt.values() if v max_freq) ans (max_freq - 1) * (n 1) max_count return max(ans, len(tasks)) if __name__ __main__: tasks list(input().strip().split()) n int(input().strip()) print(least_interval(tasks, n))注意输入格式有的版本是tasks [A,A,A,B,B,B]这种带引号和逗号的用Python的eval或者正则提取都行笔试环境里往往直接给空格分隔的字母列表用input().split()更稳妥。如果给的是一串连续大写字母如AAABBB可以直接list(input().strip())转成字符列表。4.4 常见陷阱第一max_count的统计不能漏。很多人只算出现最多的那一个任务的次数忘了可能同时有多个任务出现同样的最高频次。漏了这个A和B都出现3次、n2这类样例就会少算1个时间单位导致答案比正确值小1。第二公式里(max_freq - 1)为什么要减1因为最后一个最高频任务后面不需要再跟冷却时间任务执行完就结束了。举个例子A出现3次、n2后面两次A之间各需要2个冷却一共是2组而不是3组。所以是(max_freq - 1)乘以每组长度(n 1)末尾再加上最后一个A本身和并列最高频的任务坑位。第三ans max(ans, len(tasks))这步不能省。考虑tasks [A,B,C,D,E,F], n2所有任务都只出现一次公式算出来是(1-1) * 3 1 1但正确答案显然是6因为每个任务都必须执行一次总共6个任务就是6个单位时间。取max之后再跟6比结果就是6。5. 题目四环形数组最大子段和动态规划与环形转换5.1 题目描述与样例最后一道题是环形数组的最大子段和。给定一个整数数组nums数组是环形的也就是说nums[0]的下一个元素是nums[1]最后一个元素的下一个元素是nums[0]。求这个环形数组中连续子数组的最大和。子数组至少包含一个元素。输入 [1, -2, 3, -2] 输出 3原因是子数组[3]的和是3也可以选[3, -2, 1]和也是2最大就是3。这个例子比较温和还有一个更典型的输入 [5, -3, 5] 输出 7普通线性数组的最大子段和是5选[5]但因为是环形可以选[5, -3, 5]首尾相连跨越了数组边界和是7。这道题放在笔试最后就是为了区分“会套模板”和“真正理解动态规划”的人。你要是只会套Kadane算法的模板遇到环形扩展就懵了。别怕思路其实很干净。5.2 思路拆解把环形变成两个线性问题环形子数组的最大和只有两种情况第一种是不跨越数组边界就是普通的线性最大子段和直接Kadane算法搞定。第二种是跨越边界这时等价于“整个数组的总和”减去“数组内部的最小子段和”。可以这样理解你想选一个跨越首尾的子数组比如从数组尾部选几个元素、再从头部选几个元素中间必然有一段元素被跳过。为了让你选的子数组和最大被跳过的那一段和必须最小。所以跨越边界的最大和等于total_sum - min_subarray_sum。需要注意的边界是如果数组全部为负数那么最大子段和应该是最大的那个负数也就是Kadane算法求出的max_best。这种情况下你如果用total - min_best会因为total是负数、min_best也是负数反而得到一个更小的负数甚至正数显然是错的。所以最后要做一次判断如果max_best 0直接返回max_best。这也是这道题最大的坑——所有元素为负数时子数组不能为空你至少得选一个元素。很多人忽略了这一点用max(max_best, total - min_best)一股脑取最大值在全是负数的用例上直接翻车。5.3 Python代码实现def max_subarray_sum_circular(nums): total sum(nums) max_cur max_best nums[0] min_cur min_best nums[0] for x in nums[1:]: max_cur max(x, max_cur x) max_best max(max_best, max_cur) min_cur min(x, min_cur x) min_best min(min_best, min_cur) # 如果全是负数返回最大的负数 if max_best 0: return max_best return max(max_best, total - min_best) if __name__ __main__: nums list(map(int, input().strip().split())) print(max_subarray_sum_circular(nums))这段代码在一个循环里同时算了最大子段和与最小子段和时间复杂度O(n)空间复杂度O(1)笔试完全够用。5.4 数据规模的应对策略如果数组长度在10^5级别O(n)是唯一能过的复杂度。如果数组长度只有几百O(n²)也可能过但笔试不会有这种送分配置建议直接上O(n)写法。还有一个小变体如果题目要求返回子数组的起始和结束位置你需要在更新max_best和min_best时同步记录索引最后根据是选了线性最大还是跨越边界的最大换算成环形数组里的真实下标。这一步比较繁琐笔试一般不要求但是面试延伸考点建议提前想清楚。另一个可能出现的变体是把“最大子段和”改成“最小子段和”或“最大子序列和可以不连续”思路相通但写法不同注意区分。动态规划类的题目变种极多核心还是深刻理解状态转移。6. 实战复盘笔试现场的高频失误与应对策略6.1 高频失误速查表我把身边同学和自己在刷题群里看到的高频失分点整理成了表格你考试前可以快速扫一眼失误场景原因分析解决办法字符串解压数字多位数错乱没有累乘直接取当前位num num * 10 int(ch)出栈拼接顺序反了混淆上一层与当前层顺序记住上一层在左当前层在右滑动窗口未判断位置只判断字符是否出现过必须判断last_pos[ch] left任务调度漏并列最高频只统计一个最高频任务统计所有频次等于max的个数公式忘了取max忽略了任务总数下界ans max(ans, len(tasks))环形数组全负数报错用总和减最小子段和先判断max_best 0则直接返回输入输出格式不匹配没看清是空格分隔还是逗号分隔先用一个样例手测输入解析最后一条“输入输出格式不匹配”每年都有人栽而且往往不是核心算法的问题完全是字符串解析的锅。360笔试用的平台如果支持本地IDE调试建议先写一个简单的print(input())脚本跑一遍确认拿到的是什么格式再开始写业务逻辑。6.2 90分钟如何分配时间360春招第一批编程题一般是两小时内完成3到4道题不同批次可能略有差异。我的建议是拿到题后先花2分钟通读全部题目不要从第一题开始闷头写。通读的目的是判断哪些题是“傻瓜送分题”、哪些是“中等题”、哪些是“压轴题”然后按照先易后难的顺序作答。我个人的节奏是第一题字符串类控制在20分钟内第二题滑动窗口控制在20分钟内第三题贪心/DP控制在25分钟内剩下的时间全部留给压轴题和整体检查。如果某道题卡了15分钟还没有清晰思路果断先做后面的题不要死磕。笔试成绩是按通过用例百分比算的你写完三道简单题的80%用例往往比死磕一道难题完全AC得分更高。还有一点运行超时和内存超限是两回事。超时优先优化算法复杂度内存超限优先检查是不是开了过大的数组或递归过深。笔试平台一般会返回超时或超内存的具体提示根据提示针对性调整不要盲目重写。6.3 刷题方法和个人体会刷真题的时候别只看题解一定要动手写。这四个题我前前后后写了不下五遍每一遍都会发现新的问题第一遍漏掉max_count第二遍滑动窗口判断写成了ch in last_pos第三遍环形数组全负数忘了处理。这些问题只看题解是永远发现不了的只有亲手敲一遍才刻骨铭心。我还建议你准备一个错题本不需要多精美记录三件事题目概要和核心考点、你最初的错误思路、正确的优化思路。春招不是只考一场360这批题练熟之后你会发现很多其他公司的笔试题其实也是这几个知识点的排列组合。栈、双指针、贪心、动态规划这四块地基打牢后面刷什么题都轻松。最后说个实际体会360的笔试里题目描述有时会故意写得模糊比如不告诉你输入是空格分隔还是换行分隔、不告诉你字符串中数字有无前导零、不告诉你要不要处理空输入。这种不确定性其实也是考察点——你连输入格式都搞不定到岗后怎么面对产品经理模棱两可的需求遇到这种情况用一个最简单的样例手动推导一遍流程往往比反复读题更有效。把“手工推演”养成习惯之后你笔试的节奏感和准确率都会有一个明显的提升。