
1. 项目概述与背景分析1.1 网易有道2017内推编程题是什么先把这个标题拆开看网易有道、2017、内推、编程题。四个词放在一起核心就是当年网易有道校招内推环节给候选人做的那套在线笔试题目。内推和正常网申的区别在于内推简历会被部门直接捞起来一般可以跳过部分初筛流程候选人拿到笔试链接的时间也往往比正式批更早。所以这套题在当时的求职圈里流传度非常高很多准备进互联网大厂做开发的人不管是不是投了有道都会找出来刷一遍。这套题具体考什么我印象里涉及的内容以编程基础为主包括字符串处理、数组操作、简单数据结构运用、边界条件处理这些。从难度上看它不是那种需要背板子的竞赛题而是更贴近“给你一个业务场景让你用代码快速解决”的风格。对候选人来说能通过这道题说明基本编码能力过关、思维够清晰。1.2 为什么现在还要回头看这套题你可能会问2017年的题现在都过去这么多年了还有什么参考价值说实话这类互联网公司的内推笔试题目本身会换但考察的底层能力一直没变。字符串处理、边界控制、时间空间复杂度意识这些到现在依然是面试手撕代码的重点。尤其是对有道这样的公司业务里大量涉及文本内容处理所以笔试出字符串题是再正常不过的事。反过来看现在很多刷题平台上的题目虽然更花哨但不少从思路上都能回溯到当年这类经典题。另外这套题还有一个特殊价值它非常像2025年Python一级编程题的出题风格——围绕基础语法、简单逻辑、字符串操作展开不考复杂算法但很考验你的细节处理能力。如果你正在准备Python编程等级考试或者刚开始刷题拿这套题练手其实特别合适。1.3 什么样的人适合重点研究这套题准备参加互联网公司校招、内推的在校生尤其是目标岗位是后端开发、测试开发、客户端开发的。正在系统学习Python基础语法想通过真实题目检验自己掌握程度的初学者。备考Python等级考试一级的考生需要大量基础编程题来巩固手感。负责校招出题或面试的工程师想参考经典题型的考察点设置。这篇文章我会以Python 3为例把这类题目从读题、解题思路、代码实现到踩坑复盘完整过一遍。你不需要有很高的算法基础只要能看懂基本的for循环、if判断、字符串切片就能跟着走下来。2. 解题前的通用准备与思路框架2.1 环境准备选对语言和工具刷题之前先把环境搞定。我用的是Python 3.8直接在本地终端里运行没有依赖任何特定的IDE。如果你习惯用PyCharm、VS Code或者在线编辑器都行关键是保证写出来的代码能够在标准输入输出模式下运行。为什么强调标准输入输出因为在线笔试的判题系统基本都是通过stdin读取输入检查stdout的输出。很多新手在IDE里跑得好好的一提交就报错原因往往就是用了input()但不清楚判题系统给的输入格式或者print()的格式和题目要求不完全一致。建议你在本地也模拟这种输入方式。比如题目要求输入一个字符串你就直接在终端里粘贴要求输入多行就按行输入。这样提交的时候心里有底。2.2 拿到题目先做三件事发现很多人一看到题目就急着写代码结果写到一半发现理解偏了又推倒重来。我自己的习惯是不管题目多简单先花一两分钟做三件事明确输入格式是一行还是多行每个字段之间用什么分隔有没有可能为空明确输出格式是逐行输出还是单行输出结尾有没有空格要求框定边界情况字符串会不会是空的数组长度有没有上下限如果输入特别长会不会超时这三件事想清楚代码写起来基本不会跑偏。2.3 这类题的核心考察点网易有道这套题以及Python一级编程题核心考察点就那么几个字符串的基本操作切片、拼接、查找、替换。列表的基本操作遍历、追加、排序、去重。逻辑判断if-elif-else的嵌套与边界条件。循环控制for循环、while循环的正确使用。输入输出格式的精确控制。时间复杂度和空间复杂度的基本意识尤其是当数据量变大时你的解法能不能撑住。把这几点练扎实比背多少偏题怪题都管用。3. 典型真题拆解与完整实现3.1 题目一字符串循环移位包含问题先说一道当年流传比较广的题给定两个字符串A和B判断A循环移位后是否能包含B。什么意思呢举例来说A AABCD把A循环移位可以得到AABCD、ABCDA、BCDAA、CDAAB、DAABC等等。如果B CDAA那么显然在某次移位后的结果里是可以找到CDAA的所以应该输出True。很多人第一次看到这题第一反应是把所有循环移位的结果都生成出来然后逐个判断。这个思路对但不够好。实际上有一个非常经典的技巧如果A循环移位后的结果包含B等价于在 A A 这个字符串中能直接找到B。为什么因为 A A 已经包含了A所有循环移位可能的起点位置。比如 A AABCDA A AABCDAABCD那么任意一次循环移位的结果其实都是这个新字符串中某个长度为len(A)的连续子串。你要判断的是某个循环移位后的字符串是否包含B而B的长度可能小于A所以直接在A A中查找B就够了。代码写出来非常简单def can_shift_contain(a: str, b: str) - bool: if not a or not b: return False combined a a return b in combined if __name__ __main__: a input().strip() b input().strip() print(can_shift_contain(a, b))这里我做了空字符串的防御性判断。虽然很多题目不会专门给空字符串用例但写上没坏处。还有一点要注意input()读进来的字符串可能自带换行符或首尾空格所以统一用strip()处理一下。这个实现的时间复杂度是O(nm)其中n是A拼接后的长度m是B的长度。Python的 in 操作在字符串查找时底层做的是高效匹配对于笔试场景完全够用。3.2 题目二数组元素去重排序问题再来看一道数组题。输入一个数组里面可能有重复元素要求去重后按升序输出。这类题在Python里最简单的方式是利用set和sorted的组合def dedup_and_sort(arr): return sorted(set(arr)) if __name__ __main__: nums list(map(int, input().split())) result dedup_and_sort(nums) print( .join(map(str, result)))这段代码先把列表转成集合利用集合的互异性去重再用sorted排序。整个过程简洁可读性高时间复杂度是O(n log n)因为排序的耗时占主导。但笔试中这道题往往会有更“恶心”的输入格式。比如第一行告诉你数组长度第二行才是真正的数组元素或者元素之间用逗号分隔。这时候有人就懵了。其实思路一样只是解析的时候多写一步n int(input().strip()) nums list(map(int, input().strip().split())) result sorted(set(nums)) print( .join(map(str, result)))还有一个容易被忽略的点如果题目要求保持原有相对顺序去重就不能用set了因为set会打乱顺序。那种情况常见于要求按第一次出现顺序输出。这时候可以手动维护一个seen集合和一个结果列表def stable_dedup(arr): seen set() result [] for x in arr: if x not in seen: seen.add(x) result.append(x) return result这种写法虽然代码略多但体现了你对需求的理解深度面试时写出来反而加印象分。3.3 题目三计算字符出现次数问题还有一种出镜率极高的题给定一个字符串统计每个字符出现的次数并按某个规则输出。比如输入hello world要统计h、e、l、o、空格、w、r、d分别出现几次。这类题的经典做法是用字典def count_chars(s: str): counter {} for ch in s: counter[ch] counter.get(ch, 0) 1 return counter if __name__ __main__: s input() counter count_chars(s) for ch, cnt in counter.items(): print(f{ch}: {cnt})用dict.get(ch, 0)来写比先判断ch在不在字典里更简洁。每次读取当前计数如果不存在就默认0然后加1。如果题目要求按出现次数从高到低排序输出就加一步sortedfor ch, cnt in sorted(counter.items(), keylambda x: x[1], reverseTrue): print(f{ch}: {cnt})这里keylambda x: x[1]表示按字典的值排序reverseTrue表示降序。如果出现次数相同还想按字符顺序排可以这样写sorted(counter.items(), keylambda x: (-x[1], x[0]))技巧在于用负数表示降序同时第二个排序键x[0]保持升序。这个方法在面试中特别实用因为很多排序题都会遇到“先按频率再按字典序”的组合条件。3.4 题目四最大公约数与最小公倍数这类数学题在基础编程题里也特别常见。给定两个正整数求最大公约数GCD和最小公倍数LCM。最大公约数最经典的是辗转相除法也叫欧几里得算法。核心原理两个整数的最大公约数等于其中较小数和两数相除余数的最大公约数。递归或循环实现都行def gcd(a: int, b: int) - int: while b ! 0: a, b b, a % b return a def lcm(a: int, b: int) - int: return a * b // gcd(a, b)为什么最小公倍数可以用这个公式因为 a 和 b 的乘积等于它们的最大公约数乘以最小公倍数。注意这里要用整除//因为乘积可能超过普通整数范围但Python3的整数没有溢出问题所以直接算也没事。不过用整除更严谨。边界情况如果a或b是0gcd就没什么意义一般题目会保证输入为正整数。但如果你写工具函数还是建议加个判断def gcd_safe(a: int, b: int) - int: if a 0 or b 0: return 0 ...这个细节在正式面试手写时不一定用得上但体现了你对异常输入的处理意识。3.5 题目五回文串判断回文串就是正着读和反着读一样的字符串比如aba、level、上海自来水来自海上。判断一个字符串是否为回文串最简单的做法是def is_palindrome(s: str) - bool: return s s[::-1]s[::-1]是Python里反转字符串的写法非常方便。但这类题有时候会变体忽略大小写、忽略非字母数字字符。比如输入A man, a plan, a canal: Panama要求判断字母数字部分是否构成回文。这时候就不能直接反转了需要先过滤def is_palindrome_filtered(s: str) - bool: filtered [] for ch in s: if ch.isalnum(): filtered.append(ch.lower()) return filtered filtered[::-1]使用isalnum()判断字符是否为字母或数字再统一转成小写。这个解法不涉及额外的高级数据结构思路清晰是笔试中的标准答案。如果要求不额外使用额外空间也就是空间复杂度O(1)那就要用双指针从两端向中间扫描def is_palindrome_two_pointer(s: str) - bool: left, right 0, len(s) - 1 while left right: while left right and not s[left].isalnum(): left 1 while left right and not s[right].isalnum(): right - 1 if s[left].lower() ! s[right].lower(): return False left 1 right - 1 return True双指针写法在思路上稍微绕一点但在一些面试场景里面试官会明确要求“不能使用额外空间”这时候你就需要拿出这种方案。4. 高频变体题型与破题方法4.1 二进制中1的个数这道题在当年很多公司的笔试里都出现过。给定一个整数求它的二进制表示中有多少个1。最容易想到的思路是不断对2取模判断最后一位是不是1然后右移一位。但这里有个坑对于负数右移在Python里是算术右移会一直补1导致死循环。所以更稳妥的做法是用位运算技巧def count_one(n: int) - int: count 0 while n: n n (n - 1) count 1 return count这个技巧的原理是n (n - 1) 会把n的二进制表示中最右边的那个1变成0。所以循环一次消掉一个1循环次数等于1的个数效率很高而且天然处理了负数在Python中的无限位表示问题。更准确说Python的负数补码表示是无限长的但按位与操作后的结果会收敛所以能正常算出来。如果你想要常规写法也可以结合掩码一位一位判断def count_one_mask(n: int) - int: count 0 for i in range(32): if n (1 i): count 1 return count这种写法更直观但效率不如n (n-1)方案。笔试时首选位运算技巧因为代码短且高效。4.2 括号匹配问题括号匹配几乎是面试必考。给定一个只包含(、)、{、}、[、]的字符串判断括号是否有效也就是说左括号必须用相同类型的右括号闭合且顺序正确。这类题的标准解法是栈def is_valid_brackets(s: str) - bool: stack [] mapping {): (, }: {, ]: [} for ch in s: if ch in mapping: if not stack or stack[-1] ! mapping[ch]: return False stack.pop() else: stack.append(ch) return not stack这里用字典mapping来映射右括号对应的左括号遇到左括号就入栈遇到右括号就检查栈顶是否匹配。最后栈为空说明所有括号都正确闭合。这个题目看着简单但实际写出bug的概率不低。常见问题包括忘记判断栈为空就pop、遍历结束后忘了检查栈是否为空、只处理了小括号没处理中括号和大括号。写完后建议自己拿几个边界用例测一下比如(、)(、([)]、([])。4.3 连续子数组最大和问题这题从难度上比前面几个高一个档次但在内推题里也偶有出现。给定一个整数数组找到一个具有最大和的连续子数组返回其最大和。经典解法是Kadane算法核心思想是遍历数组维护当前子数组的和current_sum以及全局最大和max_sum。如果current_sum加上当前元素后还没有当前元素本身大那就从当前元素重新开始。def max_subarray_sum(nums): if not nums: return 0 max_sum nums[0] current_sum nums[0] for num in nums[1:]: current_sum max(num, current_sum num) max_sum max(max_sum, current_sum) return max_sum这个思路用一句话解释就是要么把当前元素加到之前的子数组后面要么抛弃之前的累加和从当前元素重新开始。之所以取max是因为如果之前的累加和是负数那加上它只会拖累当前元素。例如输入[-2,1,-3,4,-1,2,1,-5,4]遍历过程会得到最大子数组为[4,-1,2,1]和为6。你可以在草稿纸上手动推演一遍感受一下current_sum是怎么一步步变化的。5. 实战中的输入输出陷阱与应对策略5.1 多行输入测例的处理在线笔试最烦人的不是算法而是输入解析。很多时候你的算法完全没问题但程序在运行测试用例时直接报错或结果不对就是因为卡在输入上。最常见的一种情况是题目说“输入包含多组测试用例每组占两行”。这时候你不能只读一次就完事而是要用循环读到文件末尾也就是EOF。import sys for line in sys.stdin: line line.strip() if not line: continue # 假设每两组数据为一轮第一行是数组长度第二行是数组元素 n int(line) nums_line sys.stdin.readline().strip() nums list(map(int, nums_line.split())) # 处理...这里用sys.stdin而不是input()是因为在循环处理多行时sys.stdin的迭代方式更稳定也不会因为readline读到空字符串而中断。注意if not line: continue这个判断可以过滤掉空行。有些测例会在数据之间插入空行不处理的话int()会直接抛异常。5.2 字符串输入中隐藏空格的处理有些题目里的字符串是带空格的比如句子反转、单词统计。如果用input().split()它默认按空白字符分割会把多个连续空格压缩成一个同时还会过滤掉换行符。这在大多数情况下是好事但如果你要保留原始空格就要用别的办法。举个例子输入hello world中间有两个空格要求统计所有字符。这时用input().strip()可以保留字符串内部的所有空格但首尾空格会被去掉。如果题目明确说首尾也可能有空格那就连strip()都不要用直接用input()然后去掉末尾的换行符。这里有个小技巧s input().rstrip(\n)rstrip(\n)只去掉行尾的换行符不影响其他字符。这个细节在处理严格匹配输出的题目时非常重要。5.3 输出格式的精确控制输出格式是很多人的失分重灾区。比如要求“每个数字之后跟一个空格”还是“每个数字之间用一个空格行末不能有空格”。这两种要求看似相同实际输出字符串却差一个尾随空格。判题系统通常把空格也算进结果比较所以多余空格会导致Wrong Answer。推荐的做法是先把结果收集到列表里最后用join生成最终字符串result_list [str(x) for x in result] print( .join(result_list))这样就不会有行尾空格问题。如果你用的是Python 3print()默认会在结尾加换行大多数题目都接受。如果遇到某些平台要求不能有多余换行可以用print(..., end)。5.4 时间复杂度的隐形门槛有些基础题看起来直接暴力循环就能过但一提交就超时。比如数组里找重复元素如果两层循环嵌套数据量一大就崩。这种题的正确做法是利用set或者dict把时间复杂度降到O(n)。原因是在线判题系统对Python程序的运行时长限制通常比较宽但也有限度。当数组长度到10^5级别时O(n^2)的算法基本不可能通过而O(n)或O(n log n)的算法可以在1秒内跑完。有个简单的估算公式你的算法执行的基本操作次数不要超过10^7。如果n是10^5那么O(n)是10^5没问题O(n log n)大约是1.7×10^6没问题O(n^2)是10^10必挂。任何时候做题前先看一眼数据范围再决定用什么算法。6. 常见问题与排查技巧实录6.1 本地运行正确提交却报错这是我被问得最多的问题。先说结论本地正确提交报错90%是输入输出格式的问题。你可能用的是input()但测例包含多组数据需要循环读取。你可能输出了调试信息比如print(a)之类的判题系统把调试输出和答案混在一起。你可能在输出数字时带了类型括号比如print(str([1,2,3]))这会把列表的方括号也打出来。建议你在提交前做一次“干净版”检查把代码里所有print都清点一遍只保留真正要输出的内容。6.2 Python的缩进问题缩进在Python里是语法的一部分错一点就报IndentationError。笔试环境下没有IDE的自动缩进提示很容易出现tab和空格混用的情况。统一用4个空格缩进。不要用tab。虽然现代编辑器可以自动处理但在线网页编辑器里偶尔会出问题。如果提交后看到IndentationError优先检查是不是混用了缩进符。6.3 递归深度限制有些题目你可能会用递归实现比如二叉树遍历、深度优先搜索。Python的默认递归深度大约是1000层一旦超过就会抛出RecursionError。解决办法有两个一是改成循环加栈的方式二是在代码开头增加递归深度限制import sys sys.setrecursionlimit(1000000)但递归深度限制调大后可能会增加内存占用甚至导致程序崩溃。所以更好的做法是想清楚递归层数到底有多深如果可能超过1000层就要考虑非递归写法。6.4 数据类型的坑Python的int没有长度限制这是个优势但如果你习惯性地用C的思路去处理可能会写出不必要的取模运算。另外注意除法/返回的是浮点数而整除//返回的是整数。在需要精确整数运算的场景比如求最大公约数、最小公倍数一定要用//否则可能引入浮点误差。还有一点map(int, input().split())返回的是map对象在Python3里不是列表。如果你要多次使用这个结果最好先转成list。nums list(map(int, input().split()))不转列表的话第一次遍历之后map对象就空了第二次遍历什么也拿不到。这是一个非常隐蔽的小坑很多人在循环里用了一次map没问题第二次再遍历时结果为空找了半天才发现是这里的问题。6.5 常见问题速查表问题现象可能原因解决办法提交报答案错误但本地测试通过输出格式与题目要求不一致检查是否有额外空格、换行、调试输出提交报运行时错误输入格式解析错误或递归过深用sys.stdin逐行读取检查递归次数大数组用例超时算法复杂度太高用set/dict降低复杂度减少嵌套循环读取到的数据多出换行符strip()使用不当用rstrip(\n)只去除行尾换行第二次遍历map对象没数据map对象只能迭代一次先转成list再使用数组排序后结果顺序和预期不符set去重后顺序被打乱如需稳定去重手动维护seen集合6.6 我踩过的几个具体坑有一年我在一个在线笔试平台做模拟题遇到一个字符串反转的题目要求反转每个单词但保持单词顺序不变。输入是I am a student.期望输出student. a am I。我一开始写的是words input().split() print( .join(words[::-1]))本地跑没问题一提交就报错。我把题读了三遍才发现输入里可能包含多个连续空格而split()会把它们都吞掉完美还原原始空格的要求没被满足。比如原始输入是I am a student.中间有两个空格期望输出也要保留两个空格。所以后来我改成用正则分割import re parts re.split(r(\s), input())这样一来不仅单词被分割出来空格也被保留在separator中。再反转整个列表再拼接就能精确还原原始格式。这个题让我意识到很多基础题看似简单实际上考察的是你对输入数据的尊重程度。不要想当然地认为空格无关紧要在线判题系统可不会给你通融。7. 从真题看能力提升方向7.1 刷题不在多在于复盘一套网易有道2017内推题做完不要急着去刷下一套。我建议你花同样的时间复盘一遍问自己几个问题每道题我都用了几种解法最优解法的原理我能不能用大白话讲清楚我在哪些地方卡壳了卡壳的原因是知识点缺失还是思路不清晰如果题目数据规模再扩大10倍我的代码还能扛住吗把这些问题写下来比多刷十道重复题型更有用。7.2 Python基础知识点对照这套题对应的Python基础知识点我整理了一张对照表题目类型涉及基础知识点记忆关键词循环移位包含字符串拼接、in查找A A 包含所有循环移位结果数组去重排序set、sorted、map去重用set保序用手动遍历字符计数字典、get方法dict.get(key, default)最大公约数辗转相除法while b: a, b b, a % b回文判断字符串反转、双指针s[::-1] 或两端向中间扫描二进制1个数位运算n (n-1) 消除最后一个1括号匹配栈左括号入栈右括号弹栈匹配最大子数组和动态规划思想current_sum max(num, current_sum num)这些知识点如果你都能熟练运用那说明Python基础语法这块已经很扎实了。7.3 面试时的手撕代码技巧笔试之外这套题也可以当作面试手撕代码的练习素材。面试时写代码和笔试有一个很大区别面试官会看你的思考过程你一边写一边要说出为什么这么写。比如写循环移位包含那道题时你可以这样说我先把A复制一遍拼接到后面因为循环移位后的所有结果本质上都是AA这个字符串里的连续子串然后我直接在AA里查找B这样就把循环移位问题转化成了普通的子串查找问题。这样的表述比直接闷头写代码要加分得多。另外面试时写完代码一定要主动说边界情况。比如“如果输入为空字符串我这里返回False”“如果数组只有一个元素我的代码应该能正常处理”。面试官很看重这一点因为线上系统的隐藏用例往往就是这些边界值。8. 后续进阶方向与个人建议8.1 从基础题到中等难度题的过渡把这套题吃透之后下一步就是向更复杂的题型进阶。推荐按这个顺序来先把字符串类题目刷熟重点练习KMP算法、最长公共前缀、字符串压缩等。再练数组和链表掌握双指针、滑动窗口、前缀和这些高频技巧。之后是哈希表、二叉树、递归回溯这些是面试的绝对核心。最后才是动态规划和图论需要更多时间沉淀。每次进阶都不要太着急一个知识点吃透了再进入下一个。就像练武功一样扎马步都站不稳就去练轻功只会摔得更惨。8.2 保持手感的方法我个人的经验是每周至少做2到3道题保持手不生。不需要每次都做大题难题基础题反而更容易暴露出手感的下降。就像运动员每天都要做基础训练一样代码基本功也需要持续练习。做题的时候给自己限定时间。简单题控制在10分钟以内中等题控制在30分钟左右。如果超过时间还没有头绪不要硬耗直接去看题解理解思路后再自己重写一遍。8.3 写在最后的一些实话实说这些年我见过很多刷题很猛的人题库刷了上千道但真到面试现场连一道简单的字符串反转都写不利索。也见过一些刷题量不大但每道题都研究得很透的人反而能拿到不错的offer。所以刷题的数量不是关键关键是你有没有把每道题背后的思维模型真正内化。这套网易有道2017内推题带给我的收获不是某个具体的解题技巧而是一种“把复杂问题简化成基础操作”的能力。任何复杂的业务需求拆到最后都是一次次字符串处理、数组遍历、条件判断的组合。把这些基础动作练成肌肉记忆你面对新题的时候才能游刃有余。我个人在实际操作中还有一个体会把这些经典题的答案写下来隔一两周再重新写一遍你会发现第一次写的时候忽略了很多细节。第二次写的时候你对边界条件的处理会自然变得更严谨。这个过程就是进步。