360春招笔试复盘:四道编程题核心考点与代码实现 2023年360春招第一批编程题我是在周六上午蹲在出租屋里做的。说实话360的笔试系统不算卡但题目量比我预想的大两个小时四道编程题全部要自己处理输入输出。我做完的时候只剩十分钟检查最后挂在了第三题。考完把题目回忆了一遍发现这套题其实很能说明360在技术校招上的口味——不堆偏题怪题但每一道都在考察你能不能把基础算法写得滴水不漏。这篇文章就按我的复盘顺序把这批题的核心考点、解题思路、完整代码和踩坑记录梳理一遍给后面准备笔试的朋友一个可以直接参照的样本。1. 先看套题结构四道题分别考了什么1.1 题目分布和考点一览这批题从题目包装上看明显带了业务色彩不是那种“给你一个数组求xxx”的裸题而是在描述一个贴近实际工作的场景再让你抽象成算法问题。我把四道题的考点、难度和推荐用时拉了一张表方便你对照自己的薄弱环节。题号题目场景核心考点难度建议用时1日志手机号脱敏字符串处理、边界判断简单15分钟2任务调度贪心、区间排序中等25分钟3零钱凑数动态规划、完全背包中等30分钟4权限树最近公共祖先多叉树、DFS递归中等偏上40分钟从难度分布上看第一题属于送分题但送分不代表能拿满分字符串处理里隐藏的边界条件非常多后面我会专门说。第二题和第三题都是经典题型的变形一个考贪心选择一个考状态转移。第四题是唯一一道带“树结构”的题也是区分度最高的一道很多人在递归返回值设计上卡住。我记得牛客上有人讨论过这套题说第四题有点像LeetCode 236的变体但多叉树的情况更考验对递归语义的理解。我自己做的时候也明显感觉到前两题像是在热身第三题开始需要动笔推导第四题如果之前没写过LCA模板现场想清楚递归逻辑挺难的。1.2 从考点反推企业想要什么能力360这种体量的公司校招笔试不会只为了筛掉不会写代码的人更想通过题目看到你的工程素养。为什么这么说因为这四道题都有明显的“业务场景包装”日志脱敏对应安全产品的数据清洗任务调度对应后台任务编排零钱凑数对应支付系统的找零逻辑权限树对应企业安全产品的权限管理。字符串题考的是细心程度。脱敏这种需求在实际开发里太常见了字段值可能为空可能格式不对可能中间有空格。你能不能提前想到这些比能不能写出切片语句更重要。贪心题考的是“排序依据”背后的逻辑。很多人知道区间调度要先排序但为什么要按结束时间排而不是按开始时间排面试官往往会在你提交后再追问一句如果你只是背了模板很容易露馅。动态规划题考的是状态定义和边界初始化。这种题套路固定但初始化一旦写错结果就会全错。而且它不是直接在LeetCode上跑测试用例笔试系统只告诉你过没过不会告诉你哪组数据挂了排错成本很高。树题考的是递归函数对返回值的语义理解。最近公共祖先这种题最关键的不是代码多花哨而是你要清楚递归每一层返回什么、在什么条件下为空、在什么条件下返回自己。这个想不明白代码写出来也是蒙的。所以这套题整体给我的感觉是不追求知识面广度只考察最核心的基础算法能力但要求你真正理解原理而不是会默写。2. 四道题目的核心思路先不写代码2.1 字符串题日志手机号脱敏这道题的场景是这样的安全日志里会记录用户的手机号但落盘之前要把中间四位用星号替换避免敏感信息泄露。输入是一行文本包含用户ID和手机号两者用逗号分隔。输出同样用逗号分隔手机号变成类似138****5678的形式。思路本身很简单按逗号把整行割开取到手机号字段判断是不是合法的11位手机号如果是就做切片拼接str[:3] **** str[7:]。但这里有几个隐藏的坑第一用户ID里可能含有逗号。比如用户ID是user,001这种如果直接split(,)数组长度就不是2手机号位置会错乱。正确做法是限制分割次数用split(,, 1)只在第一个逗号处切开。第二手机号前后可能有空格或制表符。日志字段经常因为对齐而带上空白直接切片会把空格算进去导致长度判断失败。要先用strip()去掉空白。第三输入可能本身就是非法格式。空行、只有用户ID没有手机号、手机号不是纯数字这些都要原样返回不能报异常。笔试系统里如果抛异常直接判0分。这类字符串题的通用解法是“先清洗再校验最后处理”。不要一上来就切片先把可能影响判断的空白字符清掉再判断格式。2.2 任务调度题最多能参加多少场会议这道题讲的是运营同学要安排一批线上任务每个任务有一个开始时间和结束时间任务之间不能重叠问最多能完成多少个。实际上就是经典的区间调度问题。第一眼看到这个题很多人会想到按开始时间排序然后贪心地选最早开始的。这个思路听着合理但是反例很好构造。比如一个任务从0到10另一个任务从1到2如果按开始时间排序会先选0到10那后面1到2的任务就选不了了最优解其实是选1到2那个任务。正确做法是按结束时间从小到大排序然后依次遍历如果当前任务的开始时间不小于上一个已选任务的结束时间就可以选。为什么按结束时间贪心是对的因为结束时间越早留个后续任务的时间窗口就越大这在数学上可以证明“每次选择结束最早的可行任务”是全局最优解。笔试时不仅要写出排序逻辑还要注意两个细节一是使用自定义排序时如果开始结束时间相同可以按开始时间再排一下避免不确定行为二是区间边界是开还是闭题目里如果说“下一个任务的开始时间必须大于等于上一个任务的结束时间”那临界点就可以接上否则要用严格大于。2.3 动态规划题零钱凑数这道题说的是收银系统需要给用户找零给定几种面额的硬币每种硬币数量无限问凑出一个目标金额最少需要多少个硬币。这是典型的完全背包问题。动态规划的第一步是定义状态。用dp[i]表示凑出金额i所需的最少硬币数那么最终答案就是dp[amount]。初始化时把dp[0]设为0其他位置设为一个很大的数比如float(inf)。状态转移就是枚举最后一枚硬币的面额如果最后一枚硬币是coin那么凑出金额i需要dp[i - coin] 1个硬币。对所有面额取最小值就是dp[i] min(dp[i], dp[i - coin] 1)。这里最容易被坑的是遍历顺序。完全背包因为硬币无限应该从小到大遍历金额保证同一枚硬币可以被重复使用。如果你按01背包的方式从大到小遍历每个硬币就只能用一次结果就错了。还有一个细节是如果dp[amount]仍然是inf说明无法凑出该金额题目可能要求输出-1。这个我后面代码里会展示。2.4 树题权限树上的最近公共祖先这道题给了一个多叉树每个节点代表一个权限点子节点继承父节点的权限现在给两个权限节点要求找它们的最低公共祖先节点。最低公共祖先的意思是这个节点既是第一个权限点的祖先也是第二个权限点的祖先并且离它们最近。多叉树的最近公共祖先和二叉树的思路是一致的对根节点做DFS如果当前节点是空或者等于目标节点p或q就直接返回当前节点。然后递归遍历所有子树拿到每个子树的返回值。如果所有子树中有且仅有一个子树返回了非空节点说明两个目标节点都在这一棵子树里LCA也在那棵子树里所以把返回结果继续往上抛。如果有两个子树的返回值都非空说明两个目标节点分别位于不同子树那么当前节点就是它们的最近公共祖先。这个方法最核心的地方在于递归返回值的设计。返回值不是“找到没找到”而是“找到了的话返回什么节点”。如果你把函数的语义理解成“当前子树中是否存在目标节点”代码就会写得很绕。想清楚再动手能省很多调试时间。3. 完整代码和实现细节3.1 第一题Python3 日志脱敏我先写一版比较稳的实现前面说的边界条件都处理进去了。import sys def mask_phone(phone: str) - str: # 先去掉前后空白再判断是不是11位纯数字 phone phone.strip() if len(phone) ! 11 or not phone.isdigit(): return phone return phone[:3] **** phone[7:] def solve(): line sys.stdin.readline() if not line: return # 只在第一个逗号处分割防止用户ID本身包含逗号 parts line.strip().split(,, 1) if len(parts) 2: print(line.strip()) return uid, phone parts[0].strip(), parts[1].strip() print(f{uid},{mask_phone(phone)}) if __name__ __main__: solve()这里面我觉得最关键的是split(,, 1)。我一开始没加第二个参数直接用split(,)结果遇到ID里有逗号的数据就直接数组越界。后来改成限制分割次数一下子就稳了。另外isdigit()这个函数判断的是字符串里每个字符都是十进制数字但它对Unicode数字也会返回True如果你担心输入里有阿拉伯数字之外的字符可以再加一层phone.encode(ascii).isdecimal()的判断。笔试场景一般不用这么较真但知道这个区别能帮你避开一些诡异输入。3.2 第二题C 区间调度C写这道题需要自己定义排序规则很多同学挂在sort的cmp写错上。我用的结构体和Lambda写法如下#include bits/stdc.h using namespace std; struct Task { int start, end; }; int main() { int n; cin n; vectorTask tasks(n); for (int i 0; i n; i) { cin tasks[i].start tasks[i].end; } // 按结束时间升序如果结束时间相同按开始时间升序 sort(tasks.begin(), tasks.end(), [](const Task a, const Task b) { if (a.end ! b.end) return a.end b.end; return a.start b.start; }); int count 0; int last_end -1; // 上一个选中任务的结束时间 for (const auto t : tasks) { if (t.start last_end) { count; last_end t.end; } } cout count endl; return 0; }这里有个小问题last_end初始化为-1如果任务开始时间可以为负数这个初始值可能不对。稳妥一点可以用INT_MIN但笔试环境里一般任务时间都是非负整数所以-1也够用。有人会问为什么排序的时候结束时间相同还要按开始时间排因为如果两个任务结束时间一样你选的区间应该尽量短这样对后续任务的影响更小。虽然不按开始时间排也可能通过但加上这个排序能让贪心选择更加稳定不会因为相同结束时间的任务顺序导致答案不同。3.3 第三题Python3 一维DP零钱凑数完全背包优化的写法是把二维dp压成一维但要保持金额从前往后遍历。import sys def coin_change(coins, amount): INF float(inf) dp [INF] * (amount 1) dp[0] 0 for i in range(1, amount 1): for c in coins: if i c: dp[i] min(dp[i], dp[i - c] 1) return -1 if dp[amount] INF else dp[amount] def solve(): data sys.stdin.read().strip().split() if not data: return idx 0 n int(data[idx]); idx 1 coins [] for _ in range(n): coins.append(int(data[idx])); idx 1 amount int(data[idx]) print(coin_change(coins, amount)) if __name__ __main__: solve()我一开始把dp初始化为0结果min永远都是0整个状态转移全部失效。这种初始化错误在笔试里非常常见因为本地跑小样例时可能刚好答案是0让你误以为代码没问题。后来我习惯在写DP之前先问自己三个问题dp数组长度够不够dp[0]初始值对不对其他位置的初值会不会影响min或max另外如果你换一种遍历写法也可以先遍历硬币再遍历金额for c in coins: for i in range(c, amount 1): dp[i] min(dp[i], dp[i - c] 1)这种写法和第一种写法在求最小值时结果一样但在求方案数时会因为组合顺序不同而产生差异。笔试里如果你对完全背包的遍历顺序不够熟悉最好固定用一种不要临场混用。3.4 第四题多叉树最近公共祖先多叉树的节点定义和二叉树不同孩子是一个列表。我用C写一版重点看递归逻辑#include bits/stdc.h using namespace std; struct Node { int val; vectorNode* children; Node(int x) : val(x) {} }; Node* lca(Node* root, Node* p, Node* q) { if (root nullptr || root p || root q) { return root; } Node* found nullptr; int count 0; for (Node* child : root-children) { Node* res lca(child, p, q); if (res ! nullptr) { count; found res; } } // 如果两棵子树里都找到了目标节点说明当前节点就是LCA if (count 2) { return root; } // 否则把找到的那个节点继续向上传 return found; }代码里我用count记录非空返回值的数量。如果有两个子树的递归结果都不是空说明p和q分别位于当前节点的两个不同子树中当前节点就是它们的最近公共祖先。如果只有一个子树有结果就把那个子树的结果当作当前的返回值。如果所有子树都返回空那么当前节点返回空。这里要注意的是题目给你的是节点对象指针所以直接比较root p。如果是用节点值比较还要考虑树中是否有重复值这时候就不能简单用值相等来判断目标节点。笔试里通常会给节点ID确保不重复但最好看清题目描述。我一开始没有处理root nullptr的情况递归到叶子节点的空孩子时直接崩了。后来在函数开头加一行判空问题就解决了。很多树题的关键就是这句话没有它递归根本跑不到终止条件。4. 实战中的常见问题与避坑清单4.1 最容易翻车的边角输入春招笔试和LeetCode最大的区别在于输入输出要自己控制而这个环节能卡住一大批人。先看空白字符。我用sys.stdin.readline()读取一行如果末尾有换行符没有strip()那么字符串处理题的手机号判断就会把\n也算进去长度变成12直接判非法。这种情况本地调试时肉眼看不出来因为print输出换行后很难注意到末尾多了个空行。再看多行输入。第三题我用了sys.stdin.read().split()这样能一次性把所有token取出来不受换行影响。这个方法在数据格式比较乱的时候很管用但要注意数据量大的时候会把所有数据读进内存一般笔试不会超限面试时倒是可以提一嘴这个权衡。最后是空输入。有些考题的测试用例包含空文件或者某一行数据缺失。如果你不判断直接取下标就会数组越界。我在做题时习惯在每个数组读取之前先判断一下if not data: return虽然不一定会用到但能避免在最不该挂的地方翻车。4.2 我踩过的三个坑第一个坑是区间调度按开始时间排序。我当时心想“开始时间早的不就是先发生的吗”结果有一个用例是会议A从0到10会议B从1到2按开始时间排序后我先选了A导致B被跳过。实际最优解是选B。这个反例让我彻底记住了区间调度必须按结束时间排序。第二个坑是零钱凑数dp初始化。我把dp全部填成0然后更新的时候写dp[i] min(dp[i], dp[i-c] 1)。因为所有位置都是0min比较的结果永远是0答案永远是0。后来我把初始值改成INF再把dp[0]单独设为0才恢复正常。这种“初始化错误”比“算法想错”更隐蔽因为代码看着很顺但结果完全不可用。第三个坑是多叉树LCA没有判空。我在写递归函数时先写了if (root p || root q)然后就去递归孩子忘了最前面的root nullptr判断。当递归到叶子节点的空孩子时代码试图访问空指针的孩子列表直接运行时报错。笔试系统对运行时错误非常敏感一次崩溃就是0分。加一行判空后整个函数就健壮了很多。4.3 提交前如何快速自查我把日常做题的检查清单固定成了一套动作每道题提交前都按这个顺序过一遍输入是否有多余空白所有入口字段是否都strip()过数组是否可能为空下标访问前是否判断了长度递归函数是否有判空终止条件DP数组的初值是否会影响min/max的结果变量类型是否可能在极端情况下溢出这套清单看起来简单但能挡住80%的笔试题低级错误。特别是第一点我自己吃过的亏最多因为本地IDE里输入都是手动输入的根本不会有多余空格但在线判题系统里经常有处理不好就悄悄挂掉。5. 针对后期复习这套题给我们的启示5.1 刷题重点怎么排序如果你现在还在准备春招时间有限我建议先把基础算法的优先级排好。从这套题看字符串处理、排序贪心、动态规划、树/DFS这四个方向是绝对重点。字符串单独拿出来说是因为它最容易在边界条件上丢分而且代码量不大适合用来练手感和细心程度。然后是排序。排序本身不难但“根据什么排序”很多时候才是解题关键。比如区间调度按结束时间堆问题按优先级合并区间按起点。每次做题我都建议追问一句“为什么答案依赖这个排序规则”而不是只背“先排序再xxx”。动态规划需要集中突破。零钱凑数是完全背包里的经典题类似的还有爬楼梯、打家劫舍、编辑距离。不要贪多先把一维DP和二维DP的模板吃透再理解遍历顺序背后的意义。笔试考到DP的概率非常高但不会特别难状态转移往往一目了然。树和DFS放在最后是因为它需要一定的递归功底。多叉树LCA其实不算难但如果你连二叉树的层序遍历、前序遍历都没写过上手多叉树会很吃力。建议先把二叉树的DFS递归逻辑写熟再扩展到多叉树。5.2 从360笔试反推面试偏好这套题让我感觉到360在技术面试里大概率会追问算法“为什么”而不只是“怎么做”。你提交了区间调度的代码面试官会问你为什么结束时间贪心是对的你写了零钱DP他会问你完全背包和01背包的遍历顺序区别。所以准备笔试的时候不能只追求AC还要把每个经典算法的证明过程顺一遍。另外360的题很关注工程场景日志脱敏、任务调度、找零、权限树都是实际系统里常见的需求。如果你在面试里能主动提一句“这个算法对应我们常见的业务场景是xxx”会比单纯背答案好很多。面试官想看到的是一个能快速把业务问题抽象成算法模型的人而不是一个刷题工具。结合我这次的教训笔试前一定要在自己电脑上把输入输出模板配好。尤其Pythonsys.stdin.readline和sys.stdin.read是有区别的多行数据用后者更稳。C则要注意cin和getline混用时的换行符问题。这些看似是小事但考试时一紧张出问题的概率会放大好几倍。最后补一句个人的体会春招笔试不只是考你会不会写算法更是考你面对陌生场景时能不能冷静地拆解问题、处理边界、规范输出。360这套第一批编程题难度没有特别夸张但每一道都值得反复嚼。如果你能把这里面的坑提前排掉后面的面试至少能省下不少焦虑。

相关新闻

最新新闻

系统演进中的关键节点:从单机到微服务的架构决策指南

系统演进中的关键节点:从单机到微服务的架构决策指南

注意!系统演进中这些“重要节点”一旦错过,后面就要付出大代价 你有没有遇到过这样的场景:系统在测试环境一切正常,一上线就频繁超时;数据库 CPU 报警,DBA 凌晨三点打电话叫你起来看慢查询;每次…

2026/9/1 22:32:38
用Python验证Grok加州加德州比湾区更居中

用Python验证Grok加州加德州比湾区更居中

看到“Grok 定位加州加德州,比湾区更居中”这个标题,最容易产生两种反应:一种想讨论 Grok 的真实部署位置,另一种觉得“居中”根本说不清。这里不展开任何内部信息,只把这句话当成一道可复现的地理计算题:如…

2026/9/1 22:32:38
行空板K10+SIoTV2+Mind面板:轻量物联网图像监控实战

行空板K10+SIoTV2+Mind面板:轻量物联网图像监控实战

简介:本资源是一个面向物联网开发初学者与教育实践者的实时图像流监控系统完整实现方案,聚焦于行空板UNIHIKER K10摄像头的视频采集、高效传输及可视化集成,解决嵌入式端图像低延迟图传与跨平台监控展示的核心问题,适用于智能安防…

2026/9/1 22:32:38
西可韦X10挂耳式蓝牙耳机评测:舒适佩戴与性价比之选

西可韦X10挂耳式蓝牙耳机评测:舒适佩戴与性价比之选

这次我们来看一款在性价比和佩戴舒适度上引发热议的蓝牙耳机——西可韦 X10 挂耳式耳机。对于需要长时间佩戴耳机通勤、学习或轻度运动的朋友来说,挂耳式设计能否解决传统入耳式或头戴式的压迫感,同时保持稳定的连接和不错的音质,是大家最关心…

2026/9/1 22:32:38
小米2020校招软开笔试题复盘:核心考点与编程题解析

小米2020校招软开笔试题复盘:核心考点与编程题解析

先说明一点:手头这份标题是“小米2020校招软件开发工程师笔试题一”,并没有附带完整题目原文。所以这篇博文我是按当年小米软件研发岗校招笔试的真实风格,把最常出现的题型结构、典型考点、解题思路和现场踩坑经验做了完整复盘。如果你手上有…

2026/9/1 22:32:38
伴鱼2023秋招技术岗笔试E卷考点拆解与实战应对

伴鱼2023秋招技术岗笔试E卷考点拆解与实战应对

秋招笔试这一关,很多人以为拼的是刷题量,但我在看过不少真实笔试卷子之后发现,真正拉开差距的往往是读题速度和边界条件处理。伴鱼2023届秋招技术岗笔试E卷就是一个很典型的例子——它不考偏题怪题,但题目之间的梯度设计得很讲究&…

2026/9/1 22:27:38