
简介面向Python开发者的LeetCode全套题解与代码实现覆盖从数组、链表、树等基础数据结构到动态规划、回溯搜索、图论等进阶算法的完整练习路径适合准备技术面试、系统提升算法功底的中初级程序员。压缩包内含1160个文件主要由580个Markdown题解文档和579个Python源码文件组成另附1个.gitignore题解用于梳理思路、复杂度分析与边界条件源码提供可直接运行的实现方便对照练习与二次优化整体仅544KB轻量易取。目前已有586人浏览学习。通过配合题解逐题研读可系统经历理解题意、方案设计、编码实现到测试优化的完整链路熟练运用Python的list、dict、heapq、itertools等内建库与工具积累典型问题的高效解法和排错经验为求职面试和日常开发打下扎实基础。1. 开一个能长期维护的 leetcode 全套解答 python 版本不是背答案是沉淀解法库很多人会把leetcode全套解答python版本理解成一个装满答案的文件夹下载下来就算刷过题了。我见过太多人存了一堆题解仓库面试前翻两页遇到原题还是写不出来。真正值得做的是把这个标题当成一个工程问题用Python把LeetCode的题解整理成一套自己写得出来、跑得通、改得动的代码库。这套东西适合准备校招和社招算法面试的人也适合想把Python写扎实的工程师。它能解决的核心痛点是你缺的不是参考答案而是一条从审题到AC、可复用的落地路径。2. 为什么选Python做全套题解性能账、环境账与仓库布局2.1 Python在算法题里的性能边界什么时候该换语言选Python刷题的核心原因不是简单而是快——这个快指开发速度快。标准库自带heapq、deque、defaultdict、Counter很多解法三五行就能表达接近伪代码调试时也直观。但代价是执行速度确实比编译型语言慢。以我的经验Python每秒大概能跑1e7次简单整数操作超过这个量就要开始警惕Time Limit Exceeded。这意味着n5000的O(n^2)勉强能过n1e5就必须上O(n log n)或O(n)n1e7的O(n)已经贴着极限走。数据规模可接受复杂度Python下的表现1e3O(n^2)及以下很稳随便写1e5O(n log n)注意常数项写紧凑1e7O(n)接近极限慎用复杂对象所以在整理全套解答时第一课就是算复杂度不是看解法能不能跑通样例而是看最坏规模下的单次操作数。讨论区经常看到同款代码C过Python超时这不是Python不能做算法题而是没给Python留常数项余量。我一般会做三个默认优化循环外缓存len()、循环里用局部变量引用全局名、字符串拼接用join或列表收集而不是。如果一道题在Python下优化到头还是超时而全站Python提交的通过率也低那就果断换语言不必在常数项上死磕。2.2 用vscode把Python环境一次配好虚拟环境与调试配置先解决环境再谈题解。Python安装就一句话去官网下载3.8以上版本安装时勾选Add Python to PATH装完在终端敲python --version能输出版本号就算成了。然后打开vscode安装Python扩展ms-python用CtrlShiftP调出命令面板执行Python: Select Interpreter选对当前项目的解释器。我习惯在每个刷题目录下建独立的虚拟环境避免不同项目互相污染依赖python -m venv .venv source .venv/bin/activate python --versionWindows下的激活命令是.venv\Scripts\activate不一样。虚拟环境不是给LeetCode题解用的——题解本身只用标准库——而是为了后面装unittest增强插件、black格式化工具时不和系统Python打架。调试配置放到.vscode/launch.json{ version: 0.2.0, configurations: [ { name: Python: 当前文件, type: python, request: launch, program: ${file}, console: integratedTerminal, cwd: ${workspaceFolder} } ] }program指向当前打开的文件配合Run and Debug可以直接跑单个题解文件不用手动改配置。cwd固定为工作区根目录保证common目录里的公共模块能被import。新手最容易卡三件事解释器选错、虚拟环境没激活、launch.json里的program写死成某个具体文件名。按这个顺序排查十分钟内都能解决。2.3 题解仓库目录一个能撑住上千道题的命名规范当题解超过50道目录结构就不能随缘了。随手建一堆.py文件最后一定找不到自己写过什么。我常用的模板是leetcode-python/ ├── solutions/ │ ├── 0001_two_sum.py │ ├── 0224_basic_calculator.py │ ├── 0875_koko_eating_bananas.py │ └── 0994_rotting_oranges.py ├── hot/ │ └── README.md ├── tests/ │ ├── test_0001_two_sum.py │ ├── test_0224_basic_calculator.py │ └── test_0994_rotting_oranges.py ├── common/ │ ├── linked_list.py │ └── tree.py └── requirements.txtsolutions按四位题号加slug命名0001_two_sum.py比two_sum.py好在排序稳定、和LeetCode题号对得上后期按题号查漏很方便。hot目录放热门100题和高频题的索引面试前只看hot。tests目录用同名文件加test_前缀unittest的自动发现机制能直接批量跑。common目录放公共工具比如把列表转成链表、把数组反序列化成二叉树的代码。否则每道链表题都要重写一遍列表转链表那才是真的浪费。提示命名里别加空格、中文和括号。LeetCode的slug用短横线连接改成下划线是为了Python import方便中文文件名虽然能用但跨平台同步时容易出编码问题。3. 从单题到全套题解的三段式写法与回归测试配套3.1 单题题解的三段式审题、推演、复杂度验证一套能复现的题解代码只是最后一步。我写每一题都会在Solution类里留一个结构完整的docstring让二刷和三刷的自己能在十秒内回忆起整道题的脉络class Solution: def twoSum(self, nums: list[int], target: int) - list[int]: 题目1. Two Sum 约束2 len(nums) 10^4-10^9 nums[i] 10^9 思路哈希表存值-下标一次遍历找补值 复杂度时间 O(n)空间 O(n) seen {} for i, v in enumerate(nums): complement target - v if complement in seen: return [seen[complement], i] seen[v] i return []约束不是抄题目而是决定算法选型的依据。看到n上限是10^4O(n^2)才勉强能过看到字符串长度上限是10^5基本可以排除暴力解法。思路那一行写清为什么这么做复杂度给最坏情况。二刷时只看docstring不看实现能想起来就是真会。网上那些免费python源码大全里能跑的代码一抓一大把但没注释、没约束、没复杂度下载下来也不知道什么时候会翻车。自己按这个模板积累的题解才是真正能上战场的源码库。3.2 用unittest给每道题配回归解法改了不翻车单题调试用print就够了但整套题解必须有自动化测试。原因很简单你刷到第300题时可能会回头优化第10题的解法这时候没有回归测试改坏了都不知道。我给每道题配一个同名的test文件用unittest写最小用例集import unittest from solutions.two_sum import Solution class TestTwoSum(unittest.TestCase): def test_two_sum_standard(self): self.assertEqual(Solution().twoSum([2, 7, 11, 15], 9), [0, 1]) def test_two_sum_duplicate_values(self): # 有重复值时题目保证只有一个答案但下标顺序不确定 res Solution().twoSum([3, 3], 6) self.assertIn(res, ([0, 1], [1, 0])) def test_two_sum_negative(self): self.assertEqual(Solution().twoSum([-3, 4, 3, 90], 0), [0, 2]) if __name__ __main__: unittest.main()这里的关键是覆盖三类用例标准用例验证主路径重复值验证答案下标顺序的不确定性负数验证边界条件。assertEqual能跑顺序相关断言顺序不固定时用assertIn。测试文件命名必须是test_开头这样unittest的发现机制才能扫到。批量跑全量测试用一行命令python -m unittest discover -s tests -p test_*.py-s指定测试目录-p匹配文件名。每加一道题就多一个test文件这条命令不用改。把这条命令放进Makefile或npm script里刷题节奏就稳定下来了。注意unittest发现机制要求测试文件在tests目录下能import到solutions所以solutions目录下记得加一个空的__init__.py否则会出现ModuleNotFoundError。3.3 按热门100题和高频标签分类冲刺面试的排序策略全套解答不意味着从第1题刷到第3500题。按标签分类整理比按题号顺序刷效率高得多。我维护了一份分类表每一类标注典型题和核心技巧分类典型题核心技巧建议顺序数组/哈希两数之和、字母异位词分组哈希表、双指针1链表反转链表、合并K个有序链表迭代/递归指针2栈/队列基本计算器、有效的括号符号栈、单调栈3二叉树层序遍历、最近公共祖先DFS/BFS4二分/贪心爱吃香蕉的珂珂、跳跃游戏边界收缩、贪心证明5动态规划爬楼梯、编辑距离状态定义、转移方程6图腐烂的橘子、岛屿数量多源BFS、并查集7leetcode热门100题是第一轮重点目的是建立手感。第二轮按上表的标签补齐高频题型每个标签至少做透10道题。第三轮才去碰偏题怪题。这套排序的底层逻辑是先覆盖面试中80%的高频考点再谈覆盖面。题的全套是结果不是过程。4. 三类高频题的Python实现套路从基本计算器到腐烂的橘子4.1 栈与表达式基本计算器的符号栈写法基本计算器是栈应用的经典题原题要求实现一个支持加减和括号的表达式计算器。很多人在字符串解析上翻车不是因为逻辑多复杂而是没处理好数字累加和符号入栈的时机。我常用的写法是class Solution: def calculate(self, s: str) - int: stack [] num 0 sign 1 # 当前符号1为正-1为负 result 0 # 当前括号层级内的累加结果 for ch in s: if ch.isdigit(): num num * 10 int(ch) # 处理多位数字 elif ch : result sign * num num 0 sign 1 elif ch -: result sign * num num 0 sign -1 elif ch (: # 进入新括号先把当前结果和符号压栈 stack.append(result) stack.append(sign) result 0 sign 1 elif ch ): # 结算括号内最后一个数字 result sign * num num 0 # 弹出括号前的符号再弹出括号前的结果 result * stack.pop() result stack.pop() result sign * num return result逻辑说明result保存当前层级的累加值sign保存当前数字前的符号。遇到(把result和sign都压栈然后重置result和sign开始算括号里的内容。遇到)先结算括号内最后的num然后弹栈先弹出的是括号前的sign乘到result上恢复符号再弹出括号前的result叠加回去。这里最容易错的是弹栈顺序压栈顺序是先result后sign弹栈必须反过来。参数说明字符串里的空格直接跳过因为isdigit()和符号判断都不受空格影响num num * 10 int(ch)保证多位数像123能完整读出来遍历结束后还要补一次result sign * num否则最后一个数字不会被结算。时间复杂度O(n)空间复杂度O(n)。4.2 BFS与多源扩散腐烂的橘子的层序队列写法腐烂的橘子是LeetCode 994题目说网格里的橘子每分钟向四个方向传染一次问全部腐烂需要几分钟。这是典型的多源BFS不是DFS也不是单源BFS。因为初始腐烂的橘子可能不止一个每个腐烂橘子同时向外扩散才能算出最少的分钟数from collections import deque class Solution: def orangesRotting(self, grid: list[list[int]]) - int: m, n len(grid), len(grid[0]) q deque() fresh 0 # 第一遍扫描所有烂橘子入队统计新鲜橘子数量 for i in range(m): for j in range(n): if grid[i][j] 2: q.append((i, j)) elif grid[i][j] 1: fresh 1 if fresh 0: return 0 minutes 0 dirs [(1, 0), (-1, 0), (0, 1), (0, -1)] while q and fresh 0: minutes 1 # 关键按当前队列长度切层只处理这一分钟内的扩散 for _ in range(len(q)): x, y q.popleft() for dx, dy in dirs: nx, ny x dx, y dy if 0 nx m and 0 ny n and grid[nx][ny] 1: grid[nx][ny] 2 fresh - 1 q.append((nx, ny)) return minutes if fresh 0 else -1逻辑说明第一遍扫描把初始烂橘子全入队这是多源的起点。while循环里用for _ in range(len(q))切分当前层len(q)取的是进入这一分钟前的队列长度这样minutes加一次代表完整的一轮扩散。新鲜橘子被感染时先原地改成2避免同一个橘子被重复入队这是BFS防重的基本操作。参数说明fresh在每次感染时减1while条件里的fresh 0可以提前退出节省时间。dirs是四个方向的偏移元组边界判断用0 nx m and 0 ny nPython这种链式比较写法干净且不易错。最后如果还有新鲜橘子说明无法全腐烂返回-1。if fresh 0直接返回0是很多人会漏的边界——一开始就没有新鲜橘子答案不是-1而是0。4.3 二分答案爱吃香蕉的狒狒的上下界思维这道题在LeetCode里的正式编号是875中文译名是爱吃香蕉的珂珂刷题群里戏称爱吃香蕉的狒狒。题意是珂珂要在h小时内吃完所有piles堆香蕉求最小速度k。这题的套路叫二分答案——不直接求k而是二分k的可能范围用can_finish函数验证某个k是否可行class Solution: def minEatingSpeed(self, piles: list[int], h: int) - int: def can_finish(k: int) - bool: hours 0 for p in piles: # 上取整写法等价于 math.ceil(p / k) hours (p k - 1) // k return hours h left, right 1, max(piles) while left right: mid (left right) // 2 if can_finish(mid): right mid # mid可行试试更小的速度 else: left mid 1 # mid不可行必须更快 return left逻辑说明速度k的可行域是单调的——k越大吃完需要的总时间越少。所以二分的判断条件是hours h如果成立就把右边界收到mid继续找更小的可行速度不成立就往右移。循环结束时left就是最小可行速度这个找左侧边界的模板和普通二分查找的区别在于找到mid可行之后不立刻返回而是收紧right继续搜。参数说明上取整(p k - 1) // k比math.ceil更稳整数运算没有浮点误差。left从1开始而不是0因为速度0没有意义。right取max(piles)是因为如果要在len(piles)小时内吃完每堆至少要被当作一整堆处理速度再快也没有意义。边界case是h等于len(piles)此时答案必然等于max(piles)这个二分一样能正确收敛。时间复杂度O(n log max(piles))在n和max(piles)都到1e9级别时依然可行。5. 刷题与整理中的避坑清单5条血泪经验5.1 本地秒出结果提交就超时现象本地跑示例用例一两毫秒就出结果一提交就是Time Limit Exceeded。原因LeetCode的隐藏用例规模远大于示例。代码里藏着多余的慢操作最常见的是循环里反复调用len()、用list.pop(0)当队列用、字符串用在循环里拼接。解决队列一律用collections.dequepop(0)的O(n)耗时在数据量大时是致命的循环开头先用n len(arr)缓存长度字符串收集用列表加join。做完这三步再去看算法本身的复杂度能剪枝就剪枝。Python的常数项比C大这一点必须在设计时就想进去。5.2 递归写得好好的一提交就栈溢出现象本地小数据测试一切正常提交后直接RuntimeError: maximum recursion depth exceeded。原因Python默认递归深度只有1000。树的深度一旦超过这个值或者链表题用递归遍历必然爆栈。解决最简单的办法是递归入口前加sys.setrecursionlimit(1000000)这能救一部分场景更稳的是改迭代写法二叉树遍历用显式栈层序遍历用deque。我在写腐烂的橘子时特意用BFS就是因为这个——纯循环结构不依赖调用栈数据规模再大也不会因为递归深度翻车。5.3 内存超限莫名其妙现象代码逻辑没问题跑得也不慢但提交报Memory Limit Exceeded。原因频繁产生新对象。最典型的是二维动态规划保存了整个dp表BFS队列里存了完整状态对象或者为了省事拷贝了整个矩阵。解决二维DP只保留前一行或前两行用滚动数组代替完整表BFS队列里存小元组而不是自定义对象元组比对象省很多内存能原地修改的grid就不要dict或set额外存一份。Python对象头开销很大存1e6个整数就已经几十MB再包一层类就直接翻车。5.4 测试用例互相污染前面过了后面挂现象单独跑每个test都是绿的一起跑就出现偶发失败而且失败用例不固定。原因在Solution类里定义了类变量或者用了模块级变量存中间结果。多个测试共用同一份状态一个用例改了值另一个用例读到被改过的数据。解决类变量只放常量一律用实例属性。每个测试用例里new一个新的Solution()不要复用对象。unittest虽然会为每个测试方法重建测试类实例但它不会清理模块级变量所以模块级可变对象是重灾区尽量别用。5.5 本地样例全过提交后Wrong Answer现象给的示例全对一提交就错而且报错的用例看不见。原因输入格式理解偏差。最典型的两种字符串题里带空格没做处理数组题输入是[[1,2],[3,4]]这种字符串形式需要解析成嵌套列表才能跑。解决动手前先读Constraints一节确认输入类型和边界。遇到字符串表达式题先print(repr(s))看清空格和引号再写解析逻辑。如果题目给了多个示例把示例输入全部跑通只是起点自己再补两个边界数据比如空字符串、单个字符、负数能提前暴露一半的隐藏用例问题。6. 让这套题解库变成面试武器全量回归、随机对拍与复盘节奏6.1 对拍验证用暴力解给优化解兜底最狠的验证手段是随机对拍写一个保证正确的暴力解再随机生成海量小规模输入把暴力解的结果和优化解的结果逐一对比。这一步能抓出所有肉眼看不到的边界错误import random from solutions.two_sum import Solution def brute_two_sum(nums, target): for i in range(len(nums)): for j in range(i 1, len(nums)): if nums[i] nums[j] target: return {i, j} return None for _ in range(10000): n random.randint(2, 15) nums [random.randint(-10, 10) for _ in range(n)] target random.randint(-20, 20) expect brute_two_sum(nums, target) if expect is None: continue # 不符合题目约束跳过 actual set(Solution().twoSum(nums, target)) assert actual expect, (nums, target, expect, actual) print(对拍通过)对拍只看下标集合因为双解的顺序可能不同set比较正好抹掉顺序干扰。brute找到不到解的输入不符合题目一定有解的约束直接跳过。跑一万次随机用例如果断言全过这道题的正确性基本不用再担心。6.2 三遍复盘节奏与周赛检验我对每道题的复盘都分三遍。第一遍按标签刷热门题目写完立刻跑unittestAC后把思路和复杂度写进docstring第二遍隔一周只开空白文件不看旧解法30分钟内写出来写不出的题回炉标记第三遍在面试前只看docstring口述思路不再敲完整代码。每周打一场leeetcode周赛哪怕只AC一题也比闷头刷十题更能暴露问题——限时环境会逼你放弃完美主义先拿分再优化。我自己刷题最重的教训是把看答案当成会做。真正改观是从第三遍口述开始一道题能不看代码说清为什么用符号栈、为什么二分边界是left right才算进了脑子。希望帮到你。本文还有配套的精品资源点击获取