ARTICLE DETAIL

资讯详情

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

网易校招笔试算法题库解析:题型套路与刷题指南

网易校招笔试算法题库解析:题型套路与刷题指南 2019年秋招那会儿网易的笔试在互联网大厂里属于很有辨识度的一档题量不大但题型覆盖特别规整难度梯度拉得很长从刚开始的数组遍历送分题到后面能卡住一大半人的动态规划和贪心优化一套卷子刷下来基本能把一个人的编程底子摸得清清楚楚。标题虽然写着“合集二”但你真做完几套就会发现合集的价值从来不是背题而是帮你熟悉网易出题的那套固定套路题目描述喜欢绕弯边界条件喜欢埋坑时间限制压得比牛客其他模拟题更狠。这篇文章把我自己刷题时整理的题型结构、解题思路、踩坑记录完整写出来适合正在准备校园招聘、尤其是瞄准大厂技术岗的同学参考。1. 网易校招笔试的整体面貌与选题思路1.1 笔试环节到底在筛什么人很多人把笔试理解成“算法竞赛的迷你版”但网易这类公司的校招笔试和ACM还是有明显区别的。比赛题追求的是“在极短时间内完成极多技巧叠加”而校招笔试更看重“拿到一个业务场景能不能迅速抽象成程序结构”。同一道题放在LeetCode上可能是一道Medium放到笔试里就喜欢给你披上业务外衣。比如网易非常喜欢在题目描述里塞一堆产品文案“小易的果园里种了n棵苹果树每棵树供应周期不同”“玩家背包里有若干物品需要按规则合成”。剥掉这些壳内核其实就是排序加前缀和或者哈希表计数。所以第一关要练的不是算法难度而是读题能力。我建议拿到题目先花三分钟拆解确定输入规模、找出约束条件、判断时间复杂度上限再动手编码。笔试的另一个隐性筛选点是代码规范。网易的在线判题系统对Java和C都有严格的类名和主函数要求提交前不看编译错误一旦编译失败整题零分。我见过太多同学栽在这里类名写错、忘记导入包、输出多了空格。这些细节在平时刷LeetCode不疼不痒在校招笔试里就是致命的。1.2 题型分布与难度曲线如果把网易2019年秋招的编程题摊开看分布其实相当稳定。字符串处理、模拟题大约占三成动态规划占两成贪心加数据结构占两成剩下的是搜索、数学推导和思维题。这和阿里、百度相比少了复杂的图论和高级数据结构但多了一些对逻辑严密性的考察。难度曲线通常呈现“两个平台一个台阶”的结构。前两题属于热身题基本只要会写循环和哈希表就能拿分主要作用是稳住心态。中间的题开始考察常见算法模板比如最长上升子序列、区间合并、背包变体属于区分度最大的部分。最后一题往往是思维题代码很短但需要绕过一个关键结论很多人在这个地方卡到交卷。我的建议是练习时不要只刷题不总结要有意识地把题目归类。一道题做完后问自己三个问题它属于哪类题型核心突破口是什么如果换一个数据范围解法是否还成立这样刷二十道比盲目刷一百道更有效。2. 高频题型拆解字符串处理与模拟题2.1 字符串处理类题目的常见套路字符串题在网易笔试里出现频率极高几乎每场都有一道。这类题看起来简单但往往在细节上做文章。最典型的是“压缩与还原”模型给定一个由字母和数字组成的字符串数字表示前一个字符连续出现的次数要求在解压时处理括号嵌套和多位数字这背后其实考的是栈结构的使用。还有一种常见变体是把两个带分隔符的字符串按规则合并核心是双指针加条件判断。我个人的经验是字符串题一定要先确认字符集范围。只包含小写字母和只包含ASCII可见字符对应的数组初始化方式完全不同。用哈希表当然可以但如果字符范围明确用固定数组性能会更好。笔试环境里Java的HashMap在频繁put时会有装箱开销用int[26]往往能省下不少时间。另外字符串题最容易错的是索引边界。比如使用substring截取子串时endIndex是开区间少写一个或者多写一个都可能直接数组越界。我习惯在写完代码后手动走一遍最短输入和最长输入两个边界样例尤其是空串和单字符的情况。2.2 模拟题的读题与状态设计模拟题是网易笔试的重头戏它们不考高深算法考的是你能不能把题目描述翻译成准确的状态迁移。典型例子是“游戏角色按规则移动”“系统按时间轴处理任务队列”。这种题的核心不在代码量而在状态设计。状态多了容易乱状态少了容易漏。我的做法是先画状态转移表把每个时刻需要维护的变量列出来推演两三个简单例子确认所有分支都覆盖到再写代码。模拟题最容易翻车的地方是“一轮操作中状态被提前修改”。比如同时更新多个变量时新值覆盖旧值就会影响后续判断。这时候要么用临时变量暂存要么严格依照原值计算后一次性赋值。还有一个实用技巧模拟题如果时间步数很大一定要检查有没有循环节。有些题目表面上是模拟一万步实际上状态会在几十步后进入循环识别出循环节可以直接把复杂度从O(n)降到O(cycle)。这是网易模拟题里比较隐蔽的加分点。3. 算法核心动态规划与贪心策略3.1 动态规划的常见命题角度网易笔试里的动态规划题难度不会太夸张但非常套路化。最常出现的是“给定一个数组求满足某种约束的最大值/方案数”比如不相邻元素和的最大值、股票买卖的最佳时机、编辑距离的变体。解决这些题的关键是定义好状态含义并且明确状态转移方程的来源。我举一个高频例子打家劫舍变体数组循环首尾相连。很多同学直接套标准线性DP模板结果在环的处理上出错。正确的做法是拆成两个子问题不考虑最后一个房间和不考虑第一个房间分别跑一次线性DP再取最大值。这种“把环拆成两条链”的思路在网易笔试里反复出现过。DP题另一个容易丢分的地方是初始化。int数组默认值是0但如果求最小值初始值必须设成Integer.MAX_VALUE。同理如果状态转移里需要比较大小初始化为0可能是错误的因为某些路径根本不存在。我的习惯是画一个二维表在第一行和第一列手动填入不可达状态再开始递推。3.2 贪心策略的证明与实现贪心算法在代码实现上往往比DP短得多难的是想明白贪心策略为什么成立。网易喜欢考的贪心场景有区间调度、任务安排、分组配对。以任务安排为例每个任务有截止时间和收益同一时间只能做一个求最大收益这就不能用简单排序解决得借助优先队列动态维护。笔试里很多同学看到贪心题就直接选一个自己觉得合理的排序方式结果样例过了、提交全错。问题往往出在排序依据上。比如“按截止时间排序再按收益决策”和“按收益排序再按截止时间决策”最终结果完全不同。我的建议是在写完贪心代码后必须用一个小规模样例做验算最好尝试构造一个反例验证策略是否稳健。另外贪心题常与堆结合。比如每次从集合里取最大/最小元素合并之后又放回去这样需要用一个优先队列维护动态最值。Java里PriorityQueue默认是小顶堆如果需要大顶堆要么传Comparator.reverseOrder()要么插入负值。这个细节写错整个贪心流程就会完全走样。4. 数据结构实战哈希表、堆与单调栈4.1 哈希表在笔试题中的高频用法如果只能选一种数据结构应对笔试我会毫不犹豫选哈希表。网易的题目里到处是它的影子统计单词频次、判断元素是否存在、标记访问状态、记录元素最后一次出现的下标。哈希表的本质是空间换时间笔试中几乎不会管你用了多少额外空间所以该用就用。有一个经验值得分享在循环里使用哈希表时尽量提前计算hashCode避免重复计算。Java的HashMap在Key是String时每次查找都要重新算哈希如果循环次数超过10万性能差距就会显现。一个简单的优化是把频繁用到的key先缓存成变量而不是在循环里反复new一个相同的字符串对象。另外一个套路是用哈希表记录“前缀状态”。比如求数组中和为k的最长子数组长度经典的O(n)解法就是利用哈希表存前缀和的首次出现位置。网易经常在这个模型上做文章变换一下条件比如乘积等于k、某个条件刚好出现两次等。掌握“前缀和哈希表”的组合很多看似复杂的题都能在一分钟内定下解法方向。4.2 堆与优先队列的实战堆在网易笔试中通常不会单独出题而是作为其他算法的辅助结构出现。最典型的场景是TopK问题和大数据流中位数。一个技巧是“大小顶堆对顶结构”维护一个小顶堆存较大的一半一个大顶堆存较小的一半两个堆大小差不超过1这样堆顶就是中位数。这种思路在笔试现场非常好写而且不容易出错。优先队列还有一个妙用是“合并K个有序链表”网易有时会把它包装成“多路归并日志”之类的场景。实现上用PriorityQueue存每个链表的当前节点每次取出最小值再把它所在链表的下一个节点放入堆中复杂度是O(nlogk)。很多同学会忘记堆里放的是节点本身还是下标建议统一放一个封装对象。另一个常见错误是堆元素的比较器写反。Java的PriorityQueue默认升序如果自定义类需要按某个字段排序务必在compare方法里写清楚。我经常看到有人把return a-b写成return b-a结果取出的顺序完全相反整个算法直接失效。建议写完比较器后立刻用一个三元组做测试确保堆顶是你想要的那个元素。4.3 单调栈的典型场景单调栈是网易笔试里区分度较高的一种数据结构。它的核心应用场景是“找每个元素左边/右边第一个比它大/小的元素”典型题目是“柱状图中最大的矩形”“每日温度”。单调栈的代码模板很固定难的是理解为什么元素出栈时能确定它的边界。我拿“每日温度”举例维护一个从栈底到栈顶递减的栈遍历温度数组当当前温度大于栈顶温度时说明栈顶元素遇到了右边第一个更暖的天气这时记录天数差并弹出栈顶。这个过程每个元素至多入栈一次、出栈一次时间复杂度O(n)。很多同学会纠结“为什么弹出的元素不会再被用到”关键在于每个元素只在遇到右侧第一个更大值时弹栈一次它的结果已经确定了。单调栈题目的难点在于对“单调”的定义。不同题目可能是严格递增、非严格递减等写条件时容易出错。我的经验是先从简单样例里手推一遍确认比较符号再套模板。只要比较符号写对单调栈的代码几乎不用改。5. 实战演练三道代表性题目的完整解法5.1 题目字符串解压还原题目场景是这样描述的给定一个压缩字符串格式是“字母数字”数字可以有多位例如“a3b2c10”要求输出解压后的完整字符串“aaabbcccccccccc”。数据范围是压缩后的长度不超过10万解压后的字符串很长但不需要全部存下来只输出长度即可。这类题的关键在于搞清楚数字解析的边界。先看一个常见错误直接从左往右扫描遇到数字就循环累加字符但多位数字会被拆成多个字符处理。正确实现应该是看到数字时持续往右读直到下一个字符不是数字为止组成完整的计数。然后把这个数字乘以当前字符加入结果。public long getDecompressLength(String s) { long total 0; int i 0; int n s.length(); while (i n) { // 当前位置一定是字母 char c s.charAt(i); i; int count 0; // 解析连续的数字可能有多位 while (i n Character.isDigit(s.charAt(i))) { count count * 10 (s.charAt(i) - 0); i; } total count; } return total; }这道题如果只要长度直接累加即可但如果要求输出完整字符串需要小心内存溢出。真正的笔试环境一般不会让你输出那么长的字符串而是把结果限制在一个可接受范围内。尽管如此使用long而不是int来累加长度仍是好习惯因为一旦题目加码int很容易溢出。5.2 题目环形房屋偷窃问题这类题的场景是n个房屋围成一圈每个房屋有一定金额不能偷相邻房屋求能偷到的最大金额。由于首尾相连不能同时偷第一个和最后一个。这个限制比线性版本难解法是拆成两个子问题去掉最后一个房屋做一次线性DP再去掉第一个房屋做一次线性DP取较大值。线性DP的状态转移方程很清晰dp[i]表示偷到第i个房屋时的最大金额要么不偷当前房屋保持dp[i-1]要么偷当前房屋加上dp[i-2]和当前金额。写成代码后要注意数组长度为1和2时的边界。public int robCircle(int[] nums) { int n nums.length; if (n 1) return nums[0]; if (n 2) return Math.max(nums[0], nums[1]); // 拆成两个线性问题不偷首、不偷尾 return Math.max(robLinear(nums, 0, n - 2), robLinear(nums, 1, n - 1)); } private int robLinear(int[] nums, int start, int end) { int prev2 0, prev1 0; for (int i start; i end; i) { int cur Math.max(prev1, prev2 nums[i]); prev2 prev1; prev1 cur; } return prev1; }这里用滚动变量替代dp数组空间复杂度降到O(1)也是笔试中值得展示的优化点。边界条件是这类题最坑的地方数组长度为1时环的意义不存在直接返回唯一元素长度为2时两者不相邻取最大值即可。千万不要漏掉这些判断。5.3 题目任务调度最少时间这道题的场景是给定一个任务列表每个任务用大写字母表示同类任务之间有冷却时间n同一任务连续执行必须间隔n个单位时间不同任务不受影响求完成所有任务所需的最短时间。这是经典的贪心加数学推导问题不是模拟。关键结论是出现频率最高的任务决定了最短时间下界。假设最高频任务是A出现了maxCount次那么至少需要(maxCount - 1) * (n 1) 1个时间单位。但如果有多个任务频率相同需要把这些并列的“尾部”也考虑进去。最终答案是max(总任务数计算出的下界)。public int leastInterval(char[] tasks, int n) { int[] counts new int[26]; for (char task : tasks) { counts[task - A]; } int maxCount 0; for (int count : counts) { maxCount Math.max(maxCount, count); } int maxNum 0; for (int count : counts) { if (count maxCount) { maxNum; } } int minLen (maxCount - 1) * (n 1) maxNum; return Math.max(minLen, tasks.length); }这道题的推理过程比实现更重要。我建议大家记住这个结论但更要理解推导原理高频任务的间隔数量是maxCount-1每个间隔里至少有n个空闲或其他任务最后再补上同频率的任务尾部。笔试里如果时间充裕也可以先用优先队列模拟一遍验证答案虽然复杂一些但不容易遗漏反例。6. 常见问题与排查技巧实录6.1 时间复杂度过高的典型原因与优化思路笔试交卷后最懊恼的事不是没做出来而是“我的代码明明写对了但一直超时”。网易的题时间限制普遍是1秒或2秒Java的常数偏大如果你用一个O(n^2)的算法处理10万级别的数据基本没有生还可能。我的排查顺序是第一看数据范围。如果n大于10万尽量别用两层循环。第二看空间换时间的可能性比如用哈希表替代线性查找。第三看能否排序后利用单调性。举个例子求两数之和的变体暴力法是O(n^2)排序加双指针能降到O(nlogn)用哈希表能降到O(n)。笔试中优先选第三种。还要注意Java的IO问题。如果你用Scanner读取大量数据性能远不如BufferedReader。网易有些题目的输入规模很大Scanner可能直接导致超时。我习惯在笔试模板里直接写好BufferedReader的封装节省现场重新编写的成本。6.2 边界条件与数组越界的那些坑边界条件是我自己刷题时踩过最多的坑尤其是数组下标边界。很多题目在正常区间内测试都正确一旦遇到空数组、单元素数组、数值溢出、字符串空串就立刻崩溃。比如二分查找的结束条件写错会在数组长度为2时死循环滚动DP的索引写错会在数组长度为2时越界。一个我特别推荐的兜底策略是写完每个算法后先构造最小规模的输入手动跑一遍。最短数组是什么最长数组是什么极端值是什么。这些用例必须在提交前跑通。网易的判题系统在遇到Runtime Error时不会告诉你越界的位置只会直接报错所以自查非常重要。另有一个隐蔽问题是整数溢出。比如求中位数时写成(leftright)/2left和right都很大时会溢出应该写成left(right-left)/2。类似地累加金额、计数时优先用long避免乘积和求和在int范围内溢出这类错误在LeetCode上不常见但在网易笔试的压力环境下经常被忽略。6.3 输入输出处理与代码模板准备校招笔试的最后一关是输入输出。网易使用牛客网平台输入输出格式和LeetCode完全不同很多平时只刷LeetCode的同学第一次面对“自己写main函数、自己解析输入”时会不知所措。最常见的输入格式是第一行一个整数T表示测试用例组数然后每组数据有两行或三行。我建议提前准备好一个固定的输入模板把Scanner或BufferedReader的初始化、多组用例的处理、字符串分割和整数解析封装好每次套用即可。例如读取一行数据后直接用split( )分割成数组再逐个转成整数。BufferedReader br new BufferedReader(new InputStreamReader(System.in)); String line; while ((line br.readLine()) ! null !line.isEmpty()) { String[] parts line.split( ); int a Integer.parseInt(parts[0]); int b Integer.parseInt(parts[1]); System.out.println(a b); }还有一个容易被忽略的点题目要求输出字符串严格匹配不能有多余空格和换行。网易的多组数据输出通常要求每组占一行如果你在循环里多打印了一个空行判题系统直接判Wrong Answer。这种错误最冤务必在提交前检查输出格式。最后分享一个我自己的习惯平时刷题时我会给每道题标记三个标签——题型、数据范围、最优复杂度。遇到新题时先在脑海中检索对应的标签快速锁定解法方向。网易的笔试风格稳中有变但只要把字符串、模拟、动态规划、贪心、基础数据结构这五个大模块练透再加上一个稳定的输入输出模板通过笔试并不难。毕竟笔试只是衡量你在有限时间内解决业务问题的能力不是要求你成为算法竞赛选手。
返回列表