尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

网易内推笔试编程题全解析:四类核心算法考点与代码实战

网易内推笔试编程题全解析:四类核心算法考点与代码实战 2017年网易内推笔试的编程题合集到现在还经常被很多准备校招的人翻出来当练手素材。原因其实很简单这套题不搞偏题怪题覆盖的字符串处理、动态规划、贪心、位运算这些方向恰恰是企业内推笔试里最常见的考察点而且每道题都有“暴力能拿部分分、优化能拿满分”的层次感非常适合用来检验自己的真实水平。这篇文章我会站在一个刷题老手的角度把这套题里几个高频考点还原成可复现的典型题目给出完整的解题思路和参考代码再把最容易踩的坑挨个说清楚。无论你是刚开始准备笔试的在校生还是想系统梳理算法基础的职场人都可以直接照着练。至于题目本身是复盘多份面经后整理出的同类型题具体表述可能和原题不完全一样但考点和解题逻辑是相通的这点很重要。1. 网易内推笔试的整体设计逻辑先看懂“题外话”1.1 内推笔试和统考笔试定位完全不一样很多人有一个误区觉得内推笔试和官网统一笔试考的东西差不多随便准备一下就行。实际上这两个场景的定位差异非常大。网易的内推笔试通常启动更早一般在七八月就开始说白了就是提前锁定一部分候选人所以筛选逻辑会比统考更看重“有没有培养潜力”而不仅仅是“会不会做题”。从题目风格上看内推笔试更偏向算法思维和工程习惯的混合考察。编程题占比很高而且往往不给你太多可以钻空子的余地。它不像选择题那样能蒙代码能不能跑通、边界情况处理得干不干净一眼就能看出来。所以准备内推笔试重点不是背题而是把常见题型的思路练成条件反射。另外内推笔试有很强的“阶梯淘汰”属性。第一轮机试可能只要求过部分用例但后续面试官会直接拿你的代码来聊问“你这里为什么用 HashMap 不用数组”“你的复杂度是多少能不能优化”。这就意味着你不仅要写对还要知道自己为什么这么写。这篇文章后面的分析也会一直贯穿这个思路先讲清楚为什么这么做再给代码。1.2 题型分布与时间分配的底层逻辑复盘当时笔试的题目构成可以明显看到一个规律试卷里不会只堆难题而是由易到难分成几个梯度让不同水平的候选人都能被有效筛出来。第一梯度是热身题一般是字符串或简单模拟考察基本编码能力和细心程度。这类题不拿满分会很吃亏因为区分度恰恰体现在“简单题是不是真的写得又快又对”。第二梯度是动态规划和贪心这部分是区分度最高的区域能筛掉只会背模板、不懂变通的人。第三梯度是数学思维或位运算题面短但需要看破本质属于拉开差距的题目。时间分配上一套卷子三到四道编程题比赛时间一般控制在90到120分钟。我的建议是拿到题目先花五分钟通读全部题目不要硬着头皮从第一题开始卡。热身题大约控制在20分钟以内中档题每道25到35分钟最后一题如果15分钟内没有思路果断先把前几题的边界情况补一补确保已有代码拿稳分数。2. 四道经典真题的思路拆解从暴力到最优2.1 字符串题最长无重复字符的子串先看一道热身级别的字符串题但它考察的点其实不止滑动窗口这么简单。题目描述大概是这样的给定一个字符串请你找出其中不含有重复字符的最长子串的长度。比如输入abcabcbb最长无重复子串是abc长度是 3输入bbbbb最长子串是b长度是 1。我第一次做这个题的时候第一反应就是暴力枚举所有子串再用一个 Set 判断每个子串是否有重复字符。这个思路在字符串长度很短的时候完全可行但笔试里字符串长度往往给到 10^5 级别暴力枚举的 O(n^2) 复杂度一定会超时。正确的姿势是用滑动窗口。你可以把它想象成一个能够伸缩的窗口在字符串上从左往右滑窗口里永远保证没有重复字符。窗口右边界每往右扩展一格就看一下新字符是否已经在窗口里出现过如果出现过就把左边界挪到上一个相同字符的下一个位置保证窗口里只保留当前最长的无重复段。这里有一个特别容易翻车的细节更新左边界时不能直接用left last[c] 1而是要用left max(left, last[c] 1)。因为 last 数组里记录的是字符上一次出现的位置但这个位置可能已经不在当前窗口的有效范围里了。比如字符串abba当右边界走到第二个a时字符a上一次出现的位置是 0但此时左边界已经在 2 的位置如果直接把 left 覆盖成 1窗口就会错误地包含重复字符答案就错了。2.2 动态规划题网格最小路径和中档题里动态规划出现的频率相当高常见的一种考法是把 DP 包装在网格或矩阵场景里。题目类似这样给定一个 m 行 n 列的网格每个格子里有一个非负整数你从左上角出发每次只能向右或向下走一步到达右下角时经过的路径上所有数字之和最小是多少这个题最容易踩的坑是试图用贪心每一步都选当前格子右边或下边较小的那个数走。但贪心在这里是不成立的因为局部最优不等于全局最优。举个很简单的反例一个 2×3 的网格第一行是 1, 1, 1000第二行是 2, 1000, 1从左上角出发按贪心策略第一步会往右走但最优路径其实是先向下再向右再向下再向右。正确的思路是动态规划。设dp[i][j]表示从左上角走到格子(i, j)时的最小路径和。因为只能从上方或左方过来状态转移方程就是dp[i][j] min(dp[i-1][j], dp[i][j-1]) grid[i][j]初始化时需要注意第一行只能从左边一路走过来所以dp[0][j] dp[0][j-1] grid[0][j]第一列只能从上面一路走下来所以dp[i][0] dp[i-1][0] grid[i][0]。这是大多数初学者写错的地方因为这两个初始化没做对后面整个递推就全乱了。空间上还可以优化。观察转移方程可以发现计算当前行时只需要用到上一行的结果所以完全可以用一维数组滚动更新把空间复杂度从 O(m×n) 降到 O(n)。在笔试环境下这种空间优化不一定能影响你是否通过但面试官问起来的时候能答出这一层会加分不少。2.3 贪心题最多能参加多少个会议/活动贪心是面试里绕不开的题型网易这类公司尤其喜欢考一类区间调度问题。简化版题目如下你有 n 个会议每个会议有一个开始时间和结束时间同一时间只能参加一个会议问最多能参加多少个完整的会议。输入每行两个整数分别表示开始时间和结束时间输出一个整数表示最多能参加的会议数量。如果只是凭直觉很多人会想到按会议时长排序优先参加时间最短的。这个思路看起来对但在区间调度里是错的。更离谱的是按开始时间排序越早开始的越优先这个策略同样会翻车。正确策略是按结束时间从早到晚排序优先选结束时间早的会议然后跳过所有开始时间早于上一次已选会议结束时间的会议。为什么结束时间早就一定更好因为一个会议结束得越早给后面留下的时间越多。用数学一点的话说在所有可行解里贪心选择的第一个会议一定可以替换成结束时间最早的那个会议而不影响最优解的数量这一步交换论证是整个贪心正确性的核心面试时能讲清楚这一点会显得你对算法理解很扎实。实现上还有一个容易忽略的点如果两个会议结束时间相同怎么排序都可以只要比较器本身是自洽的不会出现compare(a,b)和compare(b,a)同时返回负数这种非法情况。如果直接用a.end - b.end做差值在两个 end 都是很大的整数时可能出现整数溢出稳妥做法是使用Integer.compare(a.end, b.end)。2.4 位运算题数组里只出现一次的数字这类题属于“一看就会一做就废”的类型因为题面非常短但要求你对底层运算有很深的理解。题目描述是给定一个非空整数数组除了某个元素只出现一次以外其余每个元素都恰好出现两次请找出那个只出现一次的元素。要求时间复杂度 O(n)空间复杂度 O(1)。看到空间复杂度 O(1)第一反应就应该排除用 HashMap 或 HashSet 的做法。这道题的标准解法是位运算把数组里所有数字做异或运算最终结果就是只出现一次的那个数字。原因是异或运算满足交换律和结合律而且一个数和自己异或等于 00 和任何数异或还等于这个数自身。于是所有成双成对的数字会全部抵消掉剩下的就是唯一落单的那个。这个题的变种也很值得准备如果除了一个元素只出现一次以外其余每个元素都恰好出现三次又该怎么找这个就得换个思路了简单异或不再适用。一个可行的方案是统计每一位上 1 出现的次数然后对每一位取模 3最后拼装出答案。这个做法时间 O(32n)空间 O(1)在笔试里也属于常考变体。3. 完整代码实现与易错点说明3.1 各题参考代码说再多不如把代码写出来。下面给出四道题的参考实现语言我混用 Java 和 Python笔试时用哪个顺手就用哪个但核心逻辑必须吃透。第一题最长无重复字符的子串用 Java 写滑动窗口public class Main { public static int longestUniqueSubstr(String s) { if (s null || s.length() 0) { return 0; } int[] last new int[128]; java.util.Arrays.fill(last, -1); int left 0, res 0; for (int i 0; i s.length(); i) { char c s.charAt(i); if (last[c] left) { left last[c] 1; } last[c] i; res Math.max(res, i - left 1); } return res; } public static void main(String[] args) { java.util.Scanner sc new java.util.Scanner(System.in); String s sc.nextLine(); System.out.println(longestUniqueSubstr(s)); } }这里我用了 128 长度的数组来存储字符上次出现的位置而不是 HashMap。因为题目如果只含 ASCII 字符数组会比 Map 快很多。如果字符串可能包含中文或其他 Unicode 字符可以把数组长度改到 65536 或者直接用 HashMap。第二题网格最小路径和用 Java 写一维滚动数组版本public class Main { public static void main(String[] args) { java.util.Scanner sc new java.util.Scanner(System.in); int m sc.nextInt(); int n sc.nextInt(); int[] dp new int[n]; for (int i 0; i m; i) { for (int j 0; j n; j) { int val sc.nextInt(); if (i 0 j 0) { dp[j] val; } else if (i 0) { dp[j] dp[j - 1] val; } else if (j 0) { dp[j] dp[j] val; } else { dp[j] Math.min(dp[j], dp[j - 1]) val; } } } System.out.println(dp[n - 1]); } }注意读取方式和 DP 更新是在同一个双重循环里完成的这样就不需要先把整个 m×n 网格都存在内存里输入多大的数据都不怕内存爆炸。第三题会议安排用 Java 实现贪心public class Main { static class Meeting { int start, end; Meeting(int s, int e) { start s; end e; } } public static void main(String[] args) { java.util.Scanner sc new java.util.Scanner(System.in); int n sc.nextInt(); Meeting[] meetings new Meeting[n]; for (int i 0; i n; i) { int s sc.nextInt(); int e sc.nextInt(); meetings[i] new Meeting(s, e); } java.util.Arrays.sort(meetings, (a, b) - Integer.compare(a.end, b.end)); int count 0; int lastEnd 0; for (Meeting mt : meetings) { if (mt.start lastEnd) { count; lastEnd mt.end; } } System.out.println(count); } }第四题找出只出现一次的数字用 Python 写def single_number(nums): res 0 for x in nums: res ^ x return res if __name__ __main__: n int(input()) arr list(map(int, input().split())) print(single_number(arr))这四段代码单独拿出来都不是很长但每一行都要做到能解释清楚“为什么这么写”尤其是边界判断和排序比较器这两处。3.2 代码里那些“看着对了但会挂”的细节笔试和平时刷 LeetCode 最大的不同是平台会对代码做很多极端测试包括超大输入、空输入、单元素输入、重复元素很多等。你就算思路全对也可能因为几个细节挂掉大半用例。第一个高频翻车点字符串题里更新左边界的逻辑。刚才已经强调过一定要用left max(left, last[c] 1)而不是直接赋值。还有一点last数组初始值必须是 -1不能是 0否则当字符在位置 0 第一次出现时你会误判它已经出现过直接把左边界向右推导致结果偏小。第二个高频翻车点DP 初始化位置。很多人写网格 DP 的时候只初始化了dp[0][0]然后从(1,1)开始循环第一行和第一列就直接变成默认值 0算出来的答案全是错的。我的习惯是先把输入读进来然后单独处理第一行和第一列最后再用双重循环计算中间部分虽然代码长一点但逻辑更清晰不容易漏。第三个高频翻车点会议题里比较器写法。Java 8 的 lambda 写起来简洁但如果你用了Comparator.comparingInt(a - a.end)这种写法最好把泛型类型写清楚否则某些老版本编译环境会报类型推断错误。另外别在比较器里用减法求差值两个 int 相减可能溢出面试官看到这种写法也会印象不好。第四个高频翻车点位运算题的输入格式。题目一般会给一个整数 n 表示数组长度下一行给 n 个数字。如果字符串里有多余空格用 Python 的 split 处理没问题用 Java 的Scanner也问题不大。但如果你用BufferedReader自己解析就一定要考虑开头的空白字符我见过有人因为没 trim 导致多读了一个空字符串程序直接异常退出。4. 笔试现场常见问题与排查技巧实录4.1 超时的锅到底背在谁身上很多同学看到“超过时间限制”就慌了觉得自己算法不对其实很多时候只是实现层面的问题。比如最长无重复子串这个题你用 HashMap 不是不行但每次charAt拿到的 char 要被自动装箱成 Character再进 Map性能开销明显高于直接操作 int 数组。笔试的数据量一大HashMap 的开销会让原本 O(n) 的算法也跑得很勉强。另一个常见的超时原因是循环里频繁调用substring。Java 的substring在旧版本里会复制底层 char 数组虽然新版本改成了共享底层数组但如果你在循环里大量截取字符串依然会产生大量临时对象GC 一频繁超时就不远了。正确做法是只维护左右下标最后再用下标计算长度而不是真的截出子串来。再说输入解析。很多笔试平台的数据量很大用Scanner读 10 万行整数时性能很差。如果发现自己的代码在输入读取阶段就花了大半时间建议换用BufferedReaderBufferedReader br new BufferedReader(new InputStreamReader(System.in)); String[] parts br.readLine().split( );这套组合拳打下来读耗时能压缩到原来的三分之一左右。笔试现场如果时间剩得不多优先检查有没有这种“白送的性能提升”。4.2 边界条件和数据溢出的翻车现场边界条件是最容易让人崩溃的本地测试全过一提交就错一堆。我总结了几类高频边界问题大家可以对照自查。第一类是空输入和单元素输入。很多题目的输入规模有下限但有些题目没说清楚你就得自己兜住。比如数组只有一个元素时异或解法能不能正确返回它本身比如字符串长度为 1 时滑动窗口能不能返回 1。这两个场景在代码里都应该是自然兼容的但如果你用了类似if (s.length() 2) return 0;这种自以为聪明的剪枝就完蛋了。第二类是整数溢出。网格路径和如果数字很大累加结果可能超过 int 范围。笔试题目如果没说数值范围优先用long接收中间结果最后输出时再考虑要不要转回 int。会议题的结束时间同理虽然 int 通常够用但用long更稳妥。第三类是负数和零的处理。位运算题里负数在 Java 和 Python 中的表现差异很大。Java 的int是有符号的右移会做符号扩展Python 的整数是无限精度的负数右移的结果和 Java 不一样。如果在读题时看到输入范围里有负数一定要先在本地把负数的测试用例跑一遍。4.3 笔试平台的操作细节平时刷题用惯了自己的 IDE到笔试平台上容易手忙脚乱。这里有几个实际经验希望你别等上了考场才想起要确认。第一主类名和包名。牛客网这类平台通常要求 Java 的主类名必须叫Main而且不能带package声明。很多人写完代码直接在本地跑忘了改类名上传之后编译都过不了白白丢送分题。第二输出格式。笔试平台一般只比对标准输出所以不要在输出里加多余提示文字比如System.out.println(结果是: ans)。这种输出一眼看过去很友好但判题系统会直接判错。第三部分得分机制。有些平台是“过多少测试点给多少分”不是非黑即白。所以即使你只能写出暴力解法也一定要交上去别空着。暴力解过了小数据用例能拿到三成到五成分数这比满盘皆输强太多了。5. 面向这套真题的备战经验与后续扩展5.1 知识点覆盖地图从题目反推需要补什么每次笔试完我都会做一个动作把遇到的题目考点画成一张“能力覆盖表”看自己哪些地方是薄弱环节。针对这套题我建议你按照下面这张表来自查。题型核心考点需要掌握的解法优先级字符串双指针、滑动窗口、哈希表最长无重复子串、最小覆盖子串高动态规划状态定义、转移方程、空间优化一维 DP、二维网格 DP、背包类高贪心排序策略、正确性证明思路区间调度、带堆的贪心中高位运算异或性质、按位统计单次出现、三次出现变体中图论/搜索BFS、DFS、拓扑排序最短步数、连通块数量中看到哪个格子是空的就专门去补哪块。比如你发现滑动窗口不熟不要只看题解至少手写三道同类型题把left指针的移动逻辑练成肌肉记忆。另外这套题里虽然没有明确出现树的题目但动态规划和贪心的思想在树形题里同样适用所以不要觉得练完这四个类型就能高枕无忧。后续可以自己扩展做做“二叉树的最大路径和”和“会议室 II”这两个变体一个是树形 DP一个是贪心加堆都属于同一个知识体系的延伸。5.2 刷题之外的三件事比多刷十道题还有用很多人的备战方式就是埋头刷题刷到后来发现常见的题都会一到新题还是懵。我的经验是除了刷题还得做三件事。第一件事是限时模拟。笔试的频率和比赛很像如果你平时做题都是不限时的上了考场很容易因为紧张而在前两道简单题上浪费太多时间。我的做法是在笔试前一周每天抽 90 分钟完整做一套模拟卷闹钟一响就停笔练的是对每道题时间的感知力。第二件事是复盘复杂度。每做完一道题不要急着看下一道先问自己这部分算法的空间复杂度是多少如果不做空间优化能不能过面试官如果问我“能不能再优化”我要怎么回答这套思维练多了面试和笔试都会明显轻松。第三件事是准备一个“错题本”。把每次笔试里因为边界条件挂掉的题记下来不用记完整代码只需要记一句话比如“DP 第一行第一列没初始化”“会议排序比较器别用减法”。开考前翻一遍能避开一大半低级失误。6. 最后分享一点我的个人体会这套网易 2017 内推笔试编程题难度放在今天看依然很扎实但并没有到劝退的程度。它真正考察的是你有没有把基础算法理解透以及你在考场高压环境下能不能保持冷静、按步就班地处理边界和优化。过了那么多年我回头看自己当初的笔试和面试最大的领悟不是哪道题该怎么做而是“会做”和“做对”之间还隔着很多练习。字符串的窗口指针、DP 的初始化、贪心的排序依据、位运算的性质这些知识点都不难难的是在有限时间里不出错。如果你能把这篇文章里的每道题都亲手写一遍把每个易错点都实际踩一遍再改对我相信你会比大多数只刷题不总结的人走得更远。
返回列表