如果你准备校招,网易这套2017秋招编程题集合,基本绕不开。我当年第一次刷的时候,第一反应是“题目文字倒不长”,结果一提交全红,后来才发现坑全在细节里:有前导零要处理,有整除要判断,还有一组数据没读完就急着输出。现在回头看,这套题很适合两类人:一类是准备参加互联网校招、想系统练算法题的同学;另一类是单纯想通过真题了解大厂命题风格、顺便巩固基础的人。今天我就把这套题里最有代表性的几道完整过一遍,从读题到建模再到代码落地,全都拆开讲。
1. 网易2017秋招编程题:这套题到底在考什么
1.1 考试形式与整体难度
网易当年的在线笔试,一般是牛客网或赛码网的环境,每个岗位一套卷子,编程题通常2到4道,时间一般90到120分钟。题目不是那种需要“灵光一闪”才能想出来的脑筋急转弯,更多是看你能不能把常见算法在有限时间内写对、写稳。
我统计过这套题的大致特点:第一道题往往是签到题,考基本语法和简单逻辑,只要不粗心就能过;中间一到两道是模拟和数学推导,核心是边界条件;最后一道会拉高一点难度,常驻选手是动态规划、贪心或搜索。整体难度在互联网大厂笔试里属于中等偏下,但通过率并没有想象中高,因为很多人不是不会做,而是挂在输入输出和极端数据上。
题型分布大致是这样的:
| 题目类型 | 考察能力 | 常见出题形式 |
|---|---|---|
| 字符串处理 | 代码基本功 | 翻转、截断、去前导零 |
| 数学逻辑 | 推导能力 | 解方程、整除判断 |
| 动态规划 | 建模能力 | 最少步数、路径问题 |
| 贪心 | 局部最优判断 | 棋盘放置、区间覆盖 |
1.2 网易的命题风格与常见套路
网易这套题有个很明显的特点:主角永远叫“小易”。题目背景全是生活场景,比如跳石板、放蛋糕、算糖果,看着轻松,实际上每一道都在考察你从文字里抽取出数学模型的能力。这种风格后来也延续到了网易游戏、网易云音乐这些产品的校招笔试里,几乎成了网易技术岗的招牌。
为什么要强调这个?因为很多同学刷题只看算法,忽略了“读题建模”这一关。实际笔试的时候,题目不会直接告诉你“这题用DP”,而是给你讲一个小故事,你得自己判断出这是一个最短路、一个背包、还是一个解方程问题。网易这套题就是最好的建模训练素材,题目不长,干扰信息少,很适合用来练“把叙述翻译成代码”的功夫。
2. 四道必会真题的完整解题思路
2.1 数字翻转:别小看签到题
这道题看起来最简单:输入一个整数,输出它的翻转结果。比如输入123输出321,输入-123输出-321,输入100输出1。但正因为简单,很多人会踩到两个坑:一个是前导零,一个是负数。
先说我见过最多的错误写法:直接对字符串做reverse,然后把反转结果转成数字,最后再处理负号。这种思路不是不行,但用字符串反转的时候很容易忽略一个点——题目给的是整数,负数翻转的意思是把绝对值的数字翻转,再恢复符号,而不是把整个字符串反转成“321-”。所以最稳妥的做法是全程用整数运算。
#include <bits/stdc++.h> using namespace std; int main() { long long n; while (cin >> n) { long long num = llabs(n); long long rev = 0; while (num > 0) { rev = rev * 10 + num % 10; num /= 10; } if (n < 0) rev = -rev; cout << rev << endl; } return 0; }这里有两个细节值得展开。第一,为什么用 long long?因为 int 的最小值是 -2147483648,如果你对它取绝对值,int 会溢出,结果还是负数,后面翻转逻辑全乱。笔试环境里一定要养成“凡是可能取反的整数,优先考虑 long long”的习惯。第二,为什么写成 while (cin >> n)?因为网易很多题目虽然描述里写着“输入一个整数”,但后台测试数据其实有多组,如果你只读一次,样例能过,提交就直接0分。所有在线笔试,我默认都按多组输入处理,这是血的教训。
2.2 计算糖果:把叙述翻译成方程
这道题讲的是:小易有一些糖果,A、B、C三个人之间做减法加减法,给你四个整数,分别是 A-B、B-C、A+B、B+C,让你反推出 A、B、C 的值。如果不存在这样的 A、B、C,就输出 No。
很多人一看到这种题就想枚举,把 A 从0试到1000,试完再试 B,最后试 C。枚举不是不行,但效率低,而且容易漏边界。正确的做法是先看出这是个线性方程组:
设四个输入分别是 x1、x2、x3、x4:
- x1 = A - B
- x2 = B - C
- x3 = A + B
- x4 = B + C
由 x1 和 x3 可以算出 A 和 B:
- A = (x1 + x3) / 2
- B = (x3 - x1) / 2
由 x2 或 x4 算 C:
- C = (x4 - x2) / 2
这道题真正的考点不是解方程,而是“判定无解”。很多同学算出了 A、B、C 就高兴地输出了,结果错了一半测试点。你可能心里想:方程都列出来了,怎么可能无解?问题在于,题目给的四个数不一定是合法方程组的输出。比如给负数、给奇数组合、或者四个数互相矛盾,都可能出现无解情况。所以必须验证:
#include <bits/stdc++.h> using namespace std; int main() { int x1, x2, x3, x4; cin >> x1 >> x2 >> x3 >> x4; int A = (x1 + x3) / 2; int B = (x3 - x1) / 2; int C = (x4 - x2) / 2; bool ok = true; if ((x1 + x3) % 2 != 0) ok = false; if ((x3 - x1) % 2 != 0) ok = false; if ((x4 - x2) % 2 != 0) ok = false; if (A < 0 || B < 0 || C < 0) ok = false; if (A - B != x1) ok = false; if (B - C != x2) ok = false; if (A + B != x3) ok = false; if (B + C != x4) ok = false; if (ok) cout << A << " " << B << " " << C << endl; else cout << "No" << endl; return 0; }我特别想强调最后那四个 if 验证。为什么算出来还要再验证一遍?因为你用 x1 和 x3 算 A、B,再用 x2 和 x4 算 C,这中间可能存在“前两个方程满足、后两个方程不满足”的情况。比如输入 1 2 3 6,解出来 A=2、B=1、C=2,但 B+C=3,不等于 x4=6,这种数据在后台测试里很常见。把验证写全,这道题才算真正做完,而不是“看起来做完了”。
2.3 跳石板:从DFS超时到动态规划
这道题是整张卷子里比较有区分度的一道。题目大意是:小易站在编号为 N 的石板上,想跳到 M 号石板,每次跳的步数必须是当前石板编号的约数,而且不能是1和它本身。问最少跳几次能到 M,到不了输出 -1。
我见过不少同学的第一反应是DFS,从 N 开始,枚举每一个约数往下递归。理论上没问题,但实际一跑就超时,因为状态空间是指数级的。比如 N=4,约数是2,跳到6;6的约数是2和3,可以跳到8或9;8的约数是2和4,跳到10或12……分支越来越多,太多重复计算。
正确的模型是最短步数问题,而且状态之间有明显的方向性——每次跳跃都从 x 跳到 x + d,其中 d > 1,所以编号只会越来越大。这种“编号递增且求最少步数”的场景,非常适合动态规划:定义 dp[i] 表示从 N 跳到 i 号石板所需的最少次数,初始 dp[N] = 0,其他地方设为一个足够大的数。然后从 N 开始往 M 遍历,对于每一个可达的位置 i,枚举 i 的所有约数 d,尝试更新 dp[i + d] = min(dp[i + d], dp[i] + 1)。
#include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; vector<int> getFactors(int x) { vector<int> res; for (int i = 2; i * i <= x; ++i) { if (x % i == 0) { res.push_back(i); if (i != x / i) { res.push_back(x / i); } } } return res; } int main() { int N, M; cin >> N >> M; vector<int> dp(M + 1, INF); dp[N] = 0; for (int i = N; i <= M; ++i) { if (dp[i] == INF) continue; vector<int> factors = getFactors(i); for (int d : factors) { if (i + d <= M) { dp[i + d] = min(dp[i + d], dp[i] + 1); } } } cout << (dp[M] == INF ? -1 : dp[M]) << endl; return 0; }这里有个关键优化:枚举约数时只需要遍历到根号 x,然后把成对的约数都加进列表。比如 x=12,遍历 i=2 时得到约数2,同时得到 12/2=6;遍历到 i=3 时得到3,同时得到4。这样就不用从2一直试到 x-1,时间复杂度从 O(Mx) 降到 O(M√M),数据范围到十万也能跑。
还有个小细节:约数列表里不包含1和自身,是因为题目要求每次跳至少前进2格。这个条件保证了状态从左到右遍历是安全的,不会出现回跳。如果你把约数理解成“所有因子”,这题就废了。我当年就是没仔细读题,把1算进去了,结果dp数组原地更新,输出了很多错误答案。
2.4 不要二:贪心标记与不可盲目套公式
“不要二”这道题名字很有意思,问题描述也很生活化:一个 W×H 的网格,每个格子里可以放一块蛋糕,任意两块蛋糕之间的欧几里得距离不能等于2,问最多能放多少块。
第一步还是要建模。欧几里得距离等于2,在整数坐标网格里只有两种情况:同一行相差两列,或者同一列相差两行。因为距离公式是 (x差)^2 + (y差)^2 = 4,满足条件的整数解只有 (2,0) 和 (0,2),不存在 (1,1) 这种组合,因为1+1=2不等于4。这个结论很关键,把几何问题直接转化成了行和列上的约束问题。
接下来是放置策略。我推荐一种特别稳的贪心写法:从左到右、从上到下遍历每一个格子,如果当前格子没有被标记过,就放一块蛋糕,然后把它同一行向右两格的位置和同一列向下两格的位置标记为不可放。为什么不需要标记左边和上边?因为如果左边或上边已经有蛋糕,当前格子早就被标记过了,根本不会走到这一步。这个写法直观、好记,而且能处理所有 W 和 H 的组合。
#include <bits/stdc++.h> using namespace std; int main() { int W, H; cin >> W >> H; vector<vector<bool>> blocked(H, vector<bool>(W, false)); int ans = 0; for (int i = 0; i < H; ++i) { for (int j = 0; j < W; ++j) { if (!blocked[i][j]) { ++ans; if (j + 2 < W) blocked[i][j + 2] = true; if (i + 2 < H) blocked[i + 2][j] = true; } } } cout << ans << endl; return 0; }有几个细节容易搞错。首先是 W 和 H 谁是谁的问题。外层循环遍历行,所以用 H 作为行数;内层遍历列,用 W 作为列数。如果你把 W 和 H 反了,小数据可能不明显,一旦测试用例给的是非对称的宽高,结果就会错。其次是 i 和 j 的越界判断,要分别用当前行和当前列去判断,不要漏写任何一个。
网上还有一种做法,说每4行能放2行、每4列能放2列,然后乘起来。我劝你不要背这个公式,因为它依赖特定的放置顺序,一旦理解偏差或者题目描述稍微变化,很容易翻车。二维标记的贪心写法最多也就是 O(WH) 的复杂度,对网易这套题的数据范围来说完全够用,而且思路清清楚楚,即使当场忘了“规律”,也能靠逻辑推出来。
3. 从题目到Offer:考场上真正决定成败的细节
3.1 输入输出与多组数据的坑
我前面反复提到多组输入,这里展开说一下。网易的在线笔试系统,很多题目看起来只给了一组样例,但后台评测数据可能是多组连续输入,也可能是单组后跟EOF结束。最保险的写法就是在外层套一个 while(cin >> n) 或者 while(scanf("%d", &n) != EOF) 的循环,把整个逻辑包进去。
C++ 还有一个性能细节:如果你用 cin/cout,建议在 main 开头加两句:
ios::sync_with_stdio(false); cin.tie(0);这两行能显著减少 IO 时间。笔试时一旦数据量到十万级别,不加这行和加这行的差距就是“险过”和“稳过”的区别。不过要注意,关闭同步之后不要再混用 printf 和 cout,否则输出顺序会乱。
如果你是Java选手,优先用 BufferedReader 和 StringTokenizer,别用 Scanner。Scanner 在读写几万行数据的时候会明显变慢,我见过不少同学算法写对了,结果因为输入解析太慢被卡超时,非常冤。
3.2 边界条件自测清单
写完代码之后,别急着提交,先在心里跑一遍边界数据。特别是这几类:
| 边界类型 | 例子 | 容易错的地方 |
|---|---|---|
| 最小值 | N=0、负数 | 取绝对值溢出、数组越界 |
| 最小值 | N=1 | 循环根本进不去,输出初值 |
| 相等 | N == M | 不需要跳,步数应该是0 |
| 无解 | 到不了 M | 输出 -1 |
| 最大值 | 接近 int 上限 | 加法溢出,要用 long long |
| 单行/单列 | W=1 或 H=1 | 二维数组的边界判断 |
拿跳石板举例,N 和 M 相等的时候,dp[M] 已经被初始化成0,你的代码要能直接输出0。拿数字翻转举例,输入是0的时候,循环 while(num > 0) 一次都不执行,rev保持0,输出0,这是对的。但如果你用 do-while 写,就会多翻转出0,看起来没区别,但如果题目要求去前导零,逻辑就可能出问题。
3.3 时间分配:先保底再冲高
网易这套题虽然总量不多,但实际笔试时你会紧张,时间会被反复调试吃掉。我的建议是,前两道题尽量在30分钟内解决,剩下时间留给动态规划这类题目。
如果遇到一道题暂时没思路,不要死磕,先写一个暴力版本,哪怕只能过30%的测试点,也比空着强。在线笔试的判分通常按测试点给分,部分通过也是分。我见过太多人从头到尾只做一道题,最后其他题全空白,连最基本的签到分都没拿到。正确策略是:先把所有题都读一遍,把会做的做掉,再做半懂不懂的,最后有时间再回来处理完全不会的。
很多同学还会犯一个错误:改完一个 bug 就直接提交,没有把改动的代码完整看一遍。有时候你为了修一个越界,不小心把输出语句挪到了循环外面,这种低级错误完全可以通过“提交前检查一遍主流程”避免。
4. 我踩过的坑与问题排查实录
4.1 样例过了,提交却0分?
这是校招笔试里最崩溃的情况,没有之一。我自己当年也经历过,后来复盘下来,原因就那几个:
第一,输出格式不一致。题目要求输出 No,你输出了 NO 或者 no;题目要求数字之间用空格分隔,你多打了换行,或者行尾多了个空格。OJ 对输出的判空非常严格,多一个空格都可能判错。这种问题样例往往发现不了,因为样例不会把每个字符都标出来。
第二,多组输入没有处理。你只读了一组数据,然后直接 return,后面几组数据全被忽略,自然0分。我在前文已经给了标准写法,这里再强调一次:所有输入,默认按多组处理,直到 EOF。
第三,数组开小了。比如跳石板,你看到 M 最大是 100000,就把 dp 开成 100000,但访问了 dp[M] 或 dp[i+d],如果 i+d 恰好等于 100001,就越界了。最稳妥的做法是开 M+5 甚至 M+10,多出来的空间反正不用,也不影响复杂度。
第四,int 溢出。计算糖果里 A = (x1 + x3) / 2,如果 x1 和 x3 都接近 int 上限,相加会直接溢出成负数,然后判断 A < 0,输出 No,但正确答案其实存在。这类问题很难通过肉眼发现,所以凡是涉及加法运算的中间结果,优先用 long long 或者 long。
4.2 DFS爆栈或递归超时:以跳石板为例
这道题很多人都卡在DFS上。我见过有同学写的递归函数估算出结果,但一提交就超时或栈溢出。原因有两个:一是分支太多,指数级增长;二是即使加了记忆化,每次递归都要维护一个 visited 数组,空间开销也不小。
用动态规划替代递归,最大的好处是每个状态只计算一次,顺序从前往后推,不需要栈帧。如果你在考场上发现递归超时,可以先想想状态能不能压缩成一维数组,能不能从左到右递推。答案是能的话,就果断改成DP。跳石板的递推方向是确定的,因为步数 d 一定大于0,所以从 N 到 M 正着推就行,不需要担心环形依赖。
4.3 网易笔试高频注意事项速查表
最后整理一张速查表,建议在笔试前翻一眼:
| 问题现象 | 可能原因 | 排查方式 |
|---|---|---|
| 样例能过但0分 | 输出多了空格或换行 | 逐字节检查输出 |
| 第一组样例过了,后面全错 | 没有循环读入多组输入 | 使用 while(cin >> n) |
| 数组越界或崩溃 | 数组大小不够 | 开到上限 + 5 |
| 数据量大时超时 | cin/cout 未优化 | 关闭流同步 |
| 结果始终是No/失败 | 中间结果溢出 | 换成 long long |
| DP结果差1 | 初始状态没设置对 | 检查 dp[N] 是否为0 |
| 读入顺序错了 | 没有按题目要求读 W、H | 打印输入参数核对 |
说实话,网易这套2017秋招编程题集合,现在拿出来看依然不过时。我在实际带人的过程中发现,能把这几道题完全吃透的人,去应付大部分互联网公司的在线笔试都很有底气。因为本质考察的从来不是题目本身,而是你遇到一个陌生描述时,能不能快速定位到熟悉的算法模型,并且把边界条件抠清楚。如果你有时间,还可以把2018年、2019年的网易笔试题也翻出来对比看看,你会发现命题风格是一脉相承的,很多题其实就是换了个故事背景。最后祝准备笔试的同学都能顺利通关,别像我当年那样,样例跑得飞快,一提交就红成一片。