简介面向华为OD机试及同类算法面试的Python编程题解合集内容取自牛客网、CSDN等平台的经典题目按题号与功能命名便于定位和对照训练适合即将应试或希望系统提升Python算法能力的开发者。资源包共106个文件以103个Python源码脚本为主另含3个文本说明文件用于题目要点与备注记录压缩包仅59KB轻量便携下载后即可在本地环境运行。题目覆盖动态规划如01背包、素数伴侣、IP地址识别、扑克牌比较等典型场景每个脚本对应一道真题或一类知识点并给出可直接运行的实现代码部分还涉及输入输出格式化、枚举统计与字典操作等细节。目前已有1592人学习代码示例覆盖了状态转移表构建、质数筛法、索引统计、字符串处理等常用技巧既能帮助初学者理解算法思维也可作为面试前的集中复习材料。整体题解贴近机试环境代码风格清晰便于反复练习、调试与复盘提升实战编码与解题效率。1. OD机试Python代码先别急着刷题把这套代码跑起来再说我拿到这套 OD 机试 Python 代码时第一反应不是翻题解而是先确认每一份代码能不能在我自己的机器上原样跑通。网上 OD 机试的题解太多了但绝大多数是往在线编辑器里一贴了事的“贴题代码”本地根本没有可执行的工程结构边界用例、输入解析、异常情况全都靠猜。这份资源不一样它是一个按题目类型整理好的本地项目每个题目一个独立 Python 文件自带测试输入输出有统一的函数入口和规范注释拿到手就能在 IDE 里断点调试也能用命令行直接批量执行。对准备大厂 OD 机试的在职开发者来说这比收藏一百篇“题解集锦”实用得多因为机试比的不是“看没看过”而是“能不能在限时和冷环境下写对”。你需要的不是背诵而是一套能反复练习、能随意改造的代码底座。2. 资源定位与技术选型这包代码解决的是“手感”问题2.1 一份软件/插件形态的 Python 题库资源这份资源从形态上说更像一个本地工具型项目而不是单纯的文档合集。它把常见机试考点按类别切片每个文件都能独立运行也支持被当成工具函数复用。整体包含字符串处理、排序与查找、数据结构模拟、经典算法模板、图论基础、动态规划和数学技巧几大块覆盖了 OD 机试里出现频率最高的题型方向。每个题目文件的头部都记录了题目描述、输入输出样例、边界条件说明和大致解法思路代码主体则尽量用最直接的 Python 写法不搞花哨语法追求的是“一次写对、边界完整”。这类资源对使用者最大的价值是“确定性”你不用再花时间在搜索引擎里从零拼凑题解也不用担心看到的代码在本地根本跑不通。每个文件按下 F5 就能执行执行完能直接看到输出结果。对二次开发来说它也足够友好——所有代码不依赖第三方库只用 Python 标准库你在任何一台装了 Python 3 的机器上都能运行不存在环境配置的玄学问题。2.2 为什么用 Python 做机试代码从实际机试场景倒推Python 是很多在职开发者准备机试时的首选语言。它的内置数据结构足够顺手列表推导式、字典默认值、堆、双端队列都开箱即用能把“想清楚逻辑”到“写出代码”之间的时间差压缩到最短。机试判题环境看的是最终结果和运行效率而 Python 的表达能力能让你把更多时间留给边界条件和极端情况。我一般会这样建议如果你的目标是 OD 机试主语言是 Python 的话平时练习时就要刻意模拟机试环境——不补全提示、没有自动导入、不能翻文档只有一个编辑器和命令行。这套资源里所有代码都按这个标准书写每个文件都直接可运行没有依赖注入也没有隐藏封装拿到就是完整的可执行脚本。它本质上是一套“拿来即用”的代码模板库你可以把它当作机试前的最后冲刺材料也可以当成日常刷题的底稿。2.3 机试代码和业务代码的差距在哪里做业务的同学准备机试时最容易翻车的地方是把团队代码规范带进考场——追求抽象封装、多层继承、注释率百分之五十。机试代码的评判标准完全不同它只看两件事功能对不对、运行超不超时。越直白的写法越不容易错越短的代码越容易调试。这套资源在代码风格上特意选择了“平铺直叙”的路线函数入口写在 main 里逻辑拆成两三个小函数必要处加中文注释不搞装饰器、不写类继承、不用 functools 之类的技巧。你把它读完之后会自然形成一种机试手感先定输入解析再写核心逻辑最后统一输出格式。这种节奏比任何抽象设计都重要——机试考的就是你在有限时间内把思路落到代码里的能力而“会读题、会解析、会输出”恰恰是这套代码反复演示的东西。3. 目录结构与代码组织每个文件都能单独跑3.1 顶层目录按题型分类文件名就是题目索引这套代码的目录设计很简单但很实用。顶层按考点分目录目录名直接对应题型分类文件名用“题号_题目缩写.py”的方式命名比如03_排序与查找/07_合并区间.py。这样的好处是你可以按目录顺序刷题也可以按自己的弱项跳着练文件在磁盘上的顺序就是知识点的递进顺序。典型的目录结构如下od_python/ ├── README.md ├── 01_字符串处理/ │ ├── 01_反转字符串.py │ ├── 02_最长公共前缀.py │ └── 03_字符串压缩.py ├── 02_排序与查找/ │ ├── 01_数组第K大.py │ ├── 02_合并区间.py │ └── 03_旋转数组最小值.py ├── 03_数据结构/ │ ├── 01_用栈实现队列.py │ ├── 02_最小栈.py │ └── 03_数组中的第K个最大元素.py ├── 04_算法模板/ │ ├── 01_0_1背包.py │ ├── 02_最长上升子序列.py │ └── 03_堆排序.py ├── 05_高频题解/ │ ├── 01_字符串解码.py │ └── 02_任务调度器.py └── utils/ ├── input_helper.py └── debug_helper.py整个资源的阅读顺序是有讲究的先字符串和排序再数据结构最后算法模板。对应到机试里字符串处理是送分题排序和查找是基础操作数据结构和算法模板才是拉开差距的地方。这样安排的好处是你能把“会一道题”变成“会一类题”从单个题解举一反三到一类考法。3.2 每个题目文件内部的固定结构约定为了让这套代码既能直接运行又方便二次修改每个题目文件都遵循一套内部约定。文件从上到下分成四个区块题目描述和输入输出样例注释、辅助函数或数据结构定义、核心解法函数、main 函数中的测试用例。核心解法函数的签名保持稳定通常接收一个参数如字符串或数组返回统一格式的结果main 里会跑三到五个测试用例。我贴一个典型的文件结构# 题目字符串压缩 # 输入: aabcccccaaa # 输出: a2b1c5a3 # 边界若压缩后长度不小于原字符串返回原字符串 def compress_string(s: str) - str: if not s: return result [] count 1 for i in range(1, len(s)): if s[i] s[i - 1]: count 1 else: result.append(s[i - 1] str(count)) count 1 result.append(s[-1] str(count)) compressed .join(result) return compressed if len(compressed) len(s) else s if __name__ __main__: tests [aabcccccaaa, abc, , a] for t in tests: print(f输入: {t!r} - 输出: {compress_string(t)!r})这个结构的核心价值在于每个文件都是一个自包含的迷你项目你可以在 main 里随意增删测试样例也可以把compress_string函数复制到自己的项目里直接使用。参数说明上s是输入字符串函数返回压缩后的字符串或原字符串空字符串、单字符、无重复字符串三种边界情况都在测试用例里显式覆盖了。3.3 utils 工具目录机试环境的“后悔药”utils目录里放的是两个辅助脚本。input_helper.py封装了标准输入读取统一处理多行输入和空行避免每个文件都重复写一遍sys.stdin.readline()的样板代码debug_helper.py用来计时和打印数组方便你在本地模拟机试时观察代码耗时。这两个工具的存在是为了解决一个很实际的痛点机试环境里你只有标准输入输出一旦输入解析写错后面的逻辑再正确也没分。input_helper.py的常见做法是提供一个read_int_list()函数自动处理一行多个数字和跨行输入的情况把解析逻辑收敛到一处而不是在每个题解里都手撕解析代码。# utils/input_helper.py import sys def read_all_ints(): 读取所有输入中的整数返回列表。 data sys.stdin.read().strip().split() return list(map(int, data)) def read_lines(): 按行读取输入去除首尾空白。 lines [] for line in sys.stdin: line line.strip() if line: lines.append(line) return lines这个工具函数解决的核心问题是“输入到底有几行”。机试的输入格式有时候说得不清不楚——可能第一行是数组长度第二行是数组元素也可能所有数据都在一行里。用sys.stdin.read()一次性读入再切分能彻底绕开这个歧义缺点是失去逐行处理的灵活性所以两个函数都提供按题目要求选。4. 核心代码拆解输入模板、算法模板与边界处理4.1 标准输入模板机试代码的第一行决定生死机试和 LeetCode 这类平台最大的不同是输入输出要自己写。LeetCode 给你函数签名你只管填逻辑机试给你一段文本你得自己解析。很多人在机试里翻车不是算法不会而是输入解析写挂了——读进来发现多了一个换行符或者数字之间夹杂空格和逗号直接把后面所有判断带偏。这套资源里最通用的输入模板是这样写的import sys def main(): # 读入所有内容按空白字符切分 data sys.stdin.read().strip().split() if not data: return # 假设第一个值是数组长度 n int(data[0]) # 剩下的是数组元素 arr list(map(int, data[1:1 n])) # 后续处理逻辑... if __name__ __main__: main()这个模板的巧妙之处在于用strip().split()一次性处理掉所有空白字符——包括空格、换行、制表符。这样无论输入是“第一行给长度第二行给元素”还是“全部挤在一行”都能被正确解析。参数说明上data是全量输入切分后的字符串列表n是数组长度arr是实际的整数数组。如果输入末尾还有多余字符arr会忽略它们不会报错这在机试环境里是件救命的事。4.2 排序查找类题目合并区间的完整实现合并区间是 OD 机试高频题也是排序类题目的代表作。这道题考察的不只是排序还有对 “区间” 这个概念的理解和边界控制。常规解法是先排序再遍历合并时间复杂度 O(n log n)空间复杂度 O(n)。from typing import List def merge(intervals: List[List[int]]) - List[List[int]]: if not intervals: return [] # 按区间起点排序 intervals.sort(keylambda x: x[0]) merged [intervals[0]] for cur in intervals[1:]: # 取已合并区间的最后一个 last merged[-1] # 当前区间起点小于等于已合并区间的终点说明有重叠 if cur[0] last[1]: # 合并终点取较大值 last[1] max(last[1], cur[1]) else: # 无重叠直接追加 merged.append(cur) return merged逻辑说明先按区间起点排序保证后续遍历时所有起点是递增的。维护一个merged列表每次取它的最后一个区间与当前区间比较。之所以只用比较最后一个是因为排序后前面的区间都已经处理完毕不存在“隔着一个区间产生重叠”的可能。cur[0] last[1]就是重叠判定的核心条件这里一定要注意是而不是因为相邻区间也算重叠——比如[1, 2]和[2, 3]应该合并成[1, 3]这是这道题最常见的边界陷阱。参数说明intervals是一个嵌套列表每个子列表表示一个区间长度为 2函数返回合并后的区间列表。测试时可以用[[1, 3], [2, 6], [8, 10], [15, 18]]验证输出应该是[[1, 6], [8, 10], [15, 18]]。也可以把intervals改成空数组[]验证边界分支是否正常工作。4.3 数据结构模拟最小栈的两种实现思路最小栈要求能在常数时间内获取栈中最小值。这是一个典型的数据结构设计题既考验栈的基本操作又考验空间换时间的思维。最常见的实现是双栈法用一个辅助栈同步记录当前最小值class MinStack: def __init__(self): self.stack [] self.min_stack [] def push(self, val: int) - None: self.stack.append(val) # 辅助栈记录当前最小值 if not self.min_stack or val self.min_stack[-1]: self.min_stack.append(val) def pop(self) - None: if self.stack: popped self.stack.pop() # 弹出的若是最小值辅助栈同步弹出 if popped self.min_stack[-1]: self.min_stack.pop() def top(self) - int: return self.stack[-1] if self.stack else None def get_min(self) - int: return self.min_stack[-1] if self.min_stack else None双栈法中stack存所有元素min_stack的栈顶永远是当前栈中的最小值。push 时判断新元素是否小于等于最小值等于的情况也要入辅助栈否则后续 pop 时会出现最小值已经被弹出但辅助栈里还留着的“对不上账”问题。pop 时通过比较弹出的值是否等于当前最小值来决定是否同步弹出辅助栈——注意这里用而不是is因为整数比较在 Python 里应该用。空间复杂度是 O(n)但换取的是 get_min 的 O(1) 时间。如果用单个栈就得在每个元素上额外保存一个当前最小值空间同样是 O(n)而且写起来容易出错。双栈法胜在思路直观、边界清晰是面试和机试中的标准答案。4.4 经典算法模板0-1 背包问题背包问题是动态规划里的基础模板也是 OD 机试中“拉分题”的常见载体。它的代码模板几乎可以一字不改地套用到多种变体上——比如分割等和子集、石头碰撞、单词拆分等。下面是最经典的 0-1 背包实现from typing import List def knapsack(weights: List[int], values: List[int], capacity: int) - int: n len(weights) # dp[i] 表示容量为 i 时的最大价值 dp [0] * (capacity 1) for i in range(n): # 逆序遍历容量防止物品被重复使用 for c in range(capacity, weights[i] - 1, -1): dp[c] max(dp[c], dp[c - weights[i]] values[i]) return dp[capacity]关键点有两个。第一个是dp数组的语义dp[c]表示当前遍历到第 i 个物品时容量为 c 的背包能装下的最大价值。第二个是内层循环为什么要逆序因为正序遍历时dp[c - weights[i]]可能已经被本轮更新过相当于同一个物品被取了多次这就不再是 0-1 背包而是完全背包了。逆序保证每次更新都基于“上一轮”的状态这是 0-1 背包代码里最容易写错也最容易考的地方。参数说明weights是物品重量列表values是物品价值列表capacity是背包总容量返回最大总价值。测试时可以用weights[2, 1, 3]、values[4, 2, 3]、capacity4手算结果是6——选重量 2 价值 4 和重量 1 价值 2 两个物品。如果你把内层循环改成range(weights[i], capacity 1)同样的输入会得到不同的结果这就是“完全背包”的写法可以对比运行一次加深印象。5. 避坑记录机试练习中容易翻车的五个细节5.1 多行输入只读了第一行导致大面积用例超时现象本地测试单个样例一切正常提交到判题系统后发现部分用例直接超时而且失败案例没有任何规律。原因机试的输入是多组测试数据但代码里只调用了一次input()或sys.stdin.readline()读到第一行就进入处理逻辑后续输入全被忽略。判题系统等不到输出自然超时。解决统一改用sys.stdin.read()全量读取拿到所有内容后再按需解析。提前把utils/input_helper.py里的read_all_ints()复制到本地环境里每道题至少跑一次多组输入确认程序能处理完所有数据再退出。5.2 输出格式有一处空格不一致直接判错现象逻辑完全正确手算输出也和样例一致提交后却是 0 分。反复检查代码找不到问题。原因判题系统对输出做的是逐字符比对多一个空格、少一个换行都算错误。常见翻车点包括行尾多打了空格、数字之间用逗号分隔而不是空格、最后一行没换行。解决每个输出语句前先用样例输入跑一遍把程序输出复制到文本编辑器里开启“显示空格”模式逐字核对。我一般会在写完题解后加一个断言把输出结果和预期字符串做严格相等比较保证本地就卡住格式问题。5.3 Python 递归深度超限现象代码用了递归实现 DFS 或分治本地小规模样例全部通过一上大数组就报RecursionError或者直接崩溃退出。原因Python 默认递归深度限制是 1000 层。机试里遇到链式结构、超长数组时递归很容易超过这个限制。解决优先把递归改成显式栈的迭代写法用while循环加列表模拟系统栈。如果确实需要递归在文件头部加sys.setrecursionlimit(1000000)临时抬高限制但这只是权宜之计机试环境未必允许你 import sys。最佳实践是写递归前先估算最大深度超过 500 就直接上迭代。5.4 不读题目要求把标准库当黑匣子滥用现象代码很短itertools.permutations一行就搞定了排列组合本地跑得飞快提交后大量用例超时。原因标准库虽然高效但它的时间复杂度是固定的。permutations生成全排列的时间复杂度是 O(n!)n 大一点就会指数爆炸这不是 Python 慢的问题而是算法本身就不该这么解。解决标准库可以用于处理数据解析、字符串操作这类低风险操作但核心算法必须自己手写并且先算一次时间复杂度。养成习惯每次使用标准库的高阶函数前先问自己它背后是什么复杂度数据规模能否承受。5.5 边界值 0 和空数组的处理总是漏掉现象测试用例里有一个n 0或空数组程序直接报下标越界或者返回了一个不符合预期的值。原因写代码时默认输入一定合法、数组一定有值没有对边界条件做显式处理。这是机试最容易忽略的问题因为样例输入通常都是正常值边界条件要靠自己补。解决每道题动笔前先列三个边界空输入、单元素输入、极值输入。在 main 函数的测试用例里固定把这三个边界放在最前面执行。比如数组类题目先判断if not arr: return 0 或某种空值字符串类题目先处理s 的情况。这套资源里所有题解都保留了边界处理代码你读的时候多看几处就形成习惯了。6. 二次开发思路把这套题库变成自己的刷题系统拿到这套资源之后最值得做的不是按顺序把所有代码读一遍而是花一个晚上把它改造成自己的“本地刷题平台”。具体做法很简单建一个tests/目录为每个题目文件写对应的 pytest 测试用例把原来散落在 main 函数里的样例断言抽出来统一用 pytest 管理。这样你能跑一次回归全部题解改动任何一个函数后能立刻知道哪些题目被影响。我再加一层计时逻辑import pytest import time from typing import List def test_merge_intervals(): from solutions import merge assert merge([[1, 3], [2, 6], [8, 10]]) [[1, 6], [8, 10]] def test_merge_intervals_edge(): from solutions import merge assert merge([]) [] def test_merge_intervals_performance(): from solutions import merge data [[i, i 2] for i in range(10000)] start time.time() merge(data) assert time.time() - start 1.0这样改造的核心收益是从“看题解”变成“跑回归”每次修改代码都有即时反馈自信心提升速度远快于死记硬背。如果你愿意还可以按考点给每个文件加上标签在 README 里维护一个从考点到题号的索引表刷题时先按考点找人再按人找解法——这比按文件顺序刷要高效得多。我自己的习惯是把每个题解的“失败版本”也留一份放在_failed/目录里旁边标注错误原因。这些失败代码比正确代码更有价值因为机试现场你不可能一次写对第二次刷题时先回忆自己上次错在哪里比重新看正确解法更能巩固记忆。从那以后我每次拿到类似的题目资源都会先做一遍“本地化重构”加测试、加计时、加失败归档把别人的题解真正变成自己的肌肉记忆。希望这套代码也能帮你找到属于自己的刷题节奏祝顺利。本文还有配套的精品资源点击获取