ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LeetCode Python题解实战:从环境配置到高频题型避坑指南

LeetCode Python题解实战:从环境配置到高频题型避坑指南 简介该资源收录LeetCode题库的Python完整解答覆盖数组、链表、树、动态规划、回溯、图论等核心算法专题适合正在备战技术面试、希望系统梳理算法知识体系的中级及以上Python开发者。包内共1160个文件主体为579个.py源码与580个.md题解笔记另有gitignore等工程辅助文件压缩包仅544KB轻量便于离线检索。目前已有586人次学习使用。每道题按题目理解、方案设计、代码实现、测试验证与复杂度优化等环节拆解代码注释与思路说明并重便于对照复盘md文档便于快速查看题目要点与解法脉络。读者可借此熟悉常见数据结构的Python写法掌握heapq、itertools、collections等标准库在算法题中的典型用法并在反复练习中强化逻辑推理与复杂度分析能力为求职笔试和日常工程编码打下扎实基础。1. LeetCode Python 题解的打开方式别把题库当成收藏夹刷 LeetCode 的人分两种一种把题库当成收藏夹收藏了就等于刷过了另一种把它当成训练场每一道题都抠到能讲清楚为止。这套 Python 版本的全套解答明显属于后者——它不只是一堆答案而是把每道题的题干、思路、AC 代码和复杂度揉进了一个个question.md里配合README.md做索引拿到手就能按图索骥。如果你正在准备算法面试或者想系统补一遍 Python 的数据结构与算法功底这套题解能省下大量从零摸黑的时间。下面我从仓库结构、环境配置讲起再落到高频题型的实现套路和踩坑记录把怎么用、怎么改、哪里会翻车一次说清。2. 这套 Python 题解的组织结构从 question.md 看作者的刷题思路2.1 仓库结构与 question.md 的定位仓库顶层是.gitignore、README.md和一批question.md。.gitignore负责忽略本地临时文件正常情况下你不需要去动它它保护的是本地的虚拟环境目录和缓存文件。README.md是入口第一次打开仓库先读这个文件里面通常写着题号覆盖范围、目录约定和刷题顺序建议。我习惯的做法是先把 README 里的题号列表导出对照自己的薄弱题型做标记再决定从哪一块切入。question.md是这套资源的核心资产每个题目对应一个文档。打开任意一个question.md你大概率会看到四块内容题目描述与输入输出格式、解题思路、Python 实现代码、时间与空间复杂度分析。这四块对应的正是刷题的完整闭环——先读懂问题再设计解法然后写代码最后评估复杂度。很多题解只给代码不给思路导致读者复现时一知半解这套文档把「为什么这么写」也写进去了这是它最值钱的地方。文件/目录作用我的使用建议README.md仓库说明与刷题索引先通读一遍确认题号范围question.md每题的题干、思路、AC 代码、复杂度按题号对照使用先盖住代码自己想.gitignore忽略本地环境与缓存文件不需要改动保持仓库干净即可这里有一个容易被忽略的细节既然每个题目都有独立的question.md你在使用时要克制住「一道题反复打开关闭」的冲动。我的习惯是每天固定挑 3 道题把对应的question.md一次性读透读的过程中记录自己的思路再和文档里的解法对比而不是漫无目的地挨个翻。2.2 为什么选择 Python内置结构、标准库与可读性这套题解选 Python 不是偶然。算法面试里写代码的速度和正确性往往比语言本身的性能更关键。Python 的内置数据结构直接对应算法题的常见操作list当数组和栈用dict当哈希表和计数器用set当去重集合用省掉了 C 里std::vector、std::unordered_map的声明步骤。我在面试时用 Python 写题平均每道题的代码量比用 Java 少 30% 左右这意味着你能把更多时间花在思考解法而不是打字上。标准库在这套题解里出镜率很高。collections.deque是 BFS 队列的标配collections.Counter处理频率统计一行搞定heapq直接实现堆结构itertools提供排列组合和累加等现成工具。functools.lru_cache更是把带记忆化的递归题从「手动开数组」解放成「加一个装饰器」。这些库让题目解法的核心逻辑更突出不会被底层实现细节淹没。不过要提醒一句Python 的简洁是把双刃剑。列表推导式和内置函数写得飞起确实能缩短代码但面试官往往会追问你「这段推导式的等价展开是什么」。所以我建议在使用这套题解时先看作者的简洁写法再自己在草稿纸上展开成基础循环版本两版都过一遍才算真正掌握。2.3 一道题从理解到 AC 的五步流程这套题解里反复出现的解题流程可以归纳成五步顺序固定。第一步是理解题目重点确认输入输出格式、数据范围、以及边界条件——比如空数组、只有一个元素、数值为负等特殊情况。第二步是设计解决方案这一步要选定数据结构和算法框架决定是用递归还是迭代用 BFS 还是 DFS用贪心还是动态规划。第三步是编写代码按照设计好的逻辑用 Python 实现。第四步是测试用例验证我一般会针对边界条件构造至少三组用例正常输入、空输入、极端值输入。第五步是优化如果提交后时间超限或空间超限回到第二步重新调整算法。这套流程看起来朴素但它保证了每一步都有明确产出不会出现「代码写完了不知道对不对」的情况。我个人的经验是把第五步单独拎出来重点对待。LeetCode 的判题系统对复杂度很敏感同样一道题O(n^2)的暴力解法可能在小数据集能过但到大数据集就超时。这时候不要急着微调代码细节先回到算法层面想清楚当前的时间复杂度瓶颈在哪能不能换一种数据结构把某个操作从O(n)降到O(1)。这套题解的文档里每一步都标注了复杂度你在复现时可以刻意对比自己的写法和作者的写法在复杂度上差多少。3. 本地复现与刷题闭环环境、调试与跑通一道 9943.1 Python 安装与 VS Code 调试配置拿到这套题解后第一件事不是刷题而是把本地环境跑通。Python 版本建议 3.8 以上太老的版本对类型注解和functools.lru_cache的支持不完整。如果你机器上还没有 Python直接去官网下载对应系统的安装包安装时勾选「Add Python to PATH」装完在终端里执行python --version确认。Mac 和 Linux 用户通常自带 Python 3但版本可能偏旧我一般会用pyenv管理版本避免系统环境被搞乱。python --version pip --version pip install pytest执行上面三条命令依次确认解释器版本、包管理工具版本、以及测试框架是否装好。我一般用pytest来跑题解附带的测试用例它是目前最主流的 Python 测试框架断言写起来直观失败信息也清晰。pip install pytest装的是最新稳定版你如果本地已有旧版本pip install -U pytest升级一下就行。调试配置上我用 VS Code 加 Python 扩展。打开仓库根目录后创建.vscode/launch.json写入下面的调试配置之后就可以在question.md对应的代码文件里打断点一行行观察变量变化。{ version: 0.2.0, configurations: [ { name: LeetCode Debug, type: debugpy, request: launch, program: ${file}, console: integratedTerminal } ] }这个配置的关键是program设为${file}这样无论你当前打开哪个题解文件按 F5 都调试那一个文件。console设为integratedTerminal可以让你在调试过程中直接输入自定义测试数据。配置好后我在question.md里建议你先建一个main入口把题目示例作为参数传进去打上断点走一遍亲眼看着程序执行路径再开始改代码。3.2 以 994 腐烂的橘子为例跑通 BFS环境就绪后选一道有代表性的题上手最有效。994 腐烂的橘子是 LeetCode 上 BFS 的经典题目几乎每份热门前一百题清单里都有它你的热搜里也高频出现。题目大意是一个网格里2代表腐烂的橘子1代表新鲜的橘子0代表空格每分钟腐烂橘子会污染上下左右四个方向的新鲜橘子问多久后所有橘子都腐烂如果有橘子始终新鲜则返回 -1。from collections import deque def orangesRotting(grid): rows, cols len(grid), len(grid[0]) queue deque() fresh 0 for i in range(rows): for j in range(cols): if grid[i][j] 2: queue.append((i, j)) elif grid[i][j] 1: fresh 1 if fresh 0: return 0 directions [(1, 0), (-1, 0), (0, 1), (0, -1)] minutes 0 while queue and fresh: for _ in range(len(queue)): x, y queue.popleft() for dx, dy in directions: nx, ny x dx, y dy if 0 nx rows and 0 ny cols and grid[nx][ny] 1: grid[nx][ny] 2 fresh - 1 queue.append((nx, ny)) minutes 1 return minutes if fresh 0 else -1代码里的queue保存当前这一分钟所有腐烂橘子的坐标fresh统计新鲜橘子的数量directions定义了四个邻接方向。每次外层while循环代表一分钟内部的for _ in range(len(queue))是关键——它保证这一分钟只处理当前队列里的节点而不是新加入的节点从而实现「分层」效果。你如果把for那层去掉改成直接while queue结果会变成按节点顺序传播分钟数就会算错。跑通这道题后我建议你在本地加三组测试空网格、全是空格、橘子被空格隔开永远无法全腐烂。第一组验证fresh 0提前返回第二组验证循环不会越界第三组验证返回 -1 的分支。这三组用例能覆盖 BFS 题绝大多数边界坑值得养成习惯。3.3 复杂度自查与提交对照每道题跑通之后我会强制自己做一次复杂度自查不急着看下一题。时间复杂度分析不能只看代码里嵌了几层循环要看数据规模随输入怎么增长。还是以 994 为例每个橘子最多入队一次、出队一次所以整体是O(rows * cols)空间复杂度也是O(rows * cols)因为最坏情况下队列里可能装下整个网格。if __name__ __main__: print(orangesRotting([[2, 1, 1], [1, 1, 0], [0, 1, 1]])) print(orangesRotting([[0, 2]]))上面这段直接跑就能看到两个结果第一个是 4第二个是 0。第一个用例对应题目自带的示例第二个覆盖了没有新鲜橘子的情形。对比题解里标注的复杂度如果自己的实现多了个不必要的排序或者反复index()查找就要警惕超时风险。这套自查习惯是把你从「能跑」推向「能过」的关键一步。4. 高频题型的 Python 实现套路热门 100 题背后的核心模型4.1 二叉树与回溯递归模板与传参边界二叉树是 LeetCode 热门 100 题里的常驻题型套路高度固定。绝大多数树的题都能套同一个递归模板处理当前节点递归处理左子树递归处理右子树。区别只在返回值的设计上——是返回高度、返回节点、还是返回布尔值。以最大深度为例代码可以短到几乎只有一行逻辑。def maxDepth(root): if not root: return 0 return max(maxDepth(root.left), maxDepth(root.right)) 1这段代码的边界条件看root是否为空空树深度为 0非空树的深度等于左右子树最大深度加 1。很多人写递归容易漏掉not root这个出口导致无限递归最后爆栈。我通常会在纸上画一棵三层的小树手动推一遍递归展开路径确认每一层返回什么、上一层拿什么做计算。递归代码的另一个坑是可变参数在回溯中的维护。比如求路径和时常见的错误做法是把路径列表直接 append 进结果数组一不注意就把引用存进去了后面的回溯会修改已保存的结果。正确做法是传入副本result.append(path[:])。这套题解里涉及回溯的题目你可以重点看作者是怎么处理「进入递归前修改状态、退出递归后还原状态」的这个前后对称的结构是回溯题不出 bug 的保障。4.2 动态规划状态定义与转移方程的书写习惯动态规划在面试里的出镜率最高也是最容易翻车的题型。我的经验是两个步骤能筛掉大部分错误第一步把状态定义写清楚是dp[i]表示前 i 个元素的最优值还是dp[i][j]表示从 i 到 j 的某个属性第二步把转移方程写成一行的数学表达式再翻译成代码。如果状态定义含糊代码写到一半一定会卡壳。def climbStairs(n): if n 2: return n dp0, dp1 1, 2 for _ in range(3, n 1): dp0, dp1 dp1, dp0 dp1 return dp1这个爬楼梯的例子展示了经典的滚动变量压缩技巧。状态转移是dp[i] dp[i-1] dp[i-2]因为dp[i]只依赖前两个值所以不需要开整个数组两个变量滚动覆盖即可。dp0, dp1 dp1, dp0 dp1这一行同时完成两件事Python 的元组赋值保证右侧先算完再统一赋值不会出现交替污染的经典 bug。使用这套题解时我会先读作者完整数组版本的代码理解转移过程再看压缩版本的代码理解空间优化两个版本都写一遍才算过。背包类、区间类 DP 的代码会更长但核心都是「状态定义 — 初始化 — 转移 — 取答案」四步。我习惯把每一步用注释标出来调试时按步骤排查错误来源。如果结果不对先看初始化是否覆盖了边界状态再看转移方程的索引是否越界最后看答案取的是哪个位置这个排查顺序能省大量时间。4.3 二分与堆073 爱吃香蕉的狒狒与 heapq二分查找在 Python 里有着最经典的运用场景。073 爱吃香蕉的狒狒是 LeetCode 上二分答案的入门必刷题题目问的是给定香蕉堆piles和守卫离开的时间h求狒狒能在h小时内吃完所有香蕉的最小速度。这个题的思路是对「速度」做二分而不是对数组做二分掌握这道题的二分框架可以解决一大类「最小化最大值」问题。def minEatingSpeed(piles, h): def can_finish(k): return sum((p k - 1) // k for p in piles) h left, right 1, max(piles) while left right: mid (left right) // 2 if can_finish(mid): right mid else: left mid 1 return leftcan_finish(k)判断用速度k是否能吃完(p k - 1) // k是向上取整的写法比math.ceil更直接也不依赖浮点运算。二分区间是[1, max(piles)]最小速度不可能小于 1也不可能大于最大堆的香蕉数。每次缩小区间时right mid和left mid 1是这套模板的固定动作两个边界处理方式不同写反了就会死循环。堆的典型场景是求 Top K、合并有序链表、以及调度类问题。heapq库是 Python 的神器heapq.heappush和heapq.heappop操作都是O(log n)比每次排序的O(n log n)高效得多。热门 100 题里的合并 K 个升序链表最佳解法就是维护一个小顶堆每次弹出当前最小的节点再把它的后继节点入堆。这套题解里凡是涉及 K 路归并的题几乎都能套这个堆模板。5. 刷题避坑清单我在这套题解上翻过车的五个地方5.1 递归爆栈RuntimeError 不是算法问题现象本地运行小用例没问题一提交就报RecursionError: maximum recursion depth exceeded我还以为是 LeetCode 判题系统的问题。原因Python 默认递归深度限制是 1000 层二叉树在极端情况下退化成链表递归深度就会超过这个限制。算法上没错但解释器不买账。解决两种方案第一种是在代码开头加sys.setrecursionlimit(10000)临时提高限制能缓解但治标不治本第二种是把递归写法改成显式栈迭代或者用尾递归优化思路重构。我现在遇到深度可能超过 1000 的题直接默认用迭代或lru_cache配合递归不赌判题系统的栈空间。这道题在本地测试时我也会故意构造一个深度 1 万层的输入验证不会爆。5.2 可变默认参数测试用例之间互相污染现象同一道题连续跑多个测试用例第三次开始结果突然不对而且错误很诡异——像是上一个用例的数据残留到了下一个用例里。原因Python 函数的默认参数在定义时只评估一次。如果写成def dfs(node, visited[])这个空列表是全局共享的第一个用例往里面塞了数据第二个用例拿到的就不是空列表。解决所有可变默认参数一律改成None函数内部再初始化。def dfs(node, visitedNone)进函数第一行写if visited is None: visited []。从那以后我每次写递归函数看到默认参数里有列表、字典、集合都强制自己停下来改掉。这套题解里的代码如果出现这种写法说明作者也有过同样的坑你可以对照着看有没有踩到。5.3 切片当常数时间用大数据集超时的元凶现象代码逻辑和题解完全一样提交就是超时但小用例本地跑得飞快百思不得其解。原因Python 的列表切片arr[1:]会创建一个新列表时间复杂度是O(n)。在递归或循环里高频使用切片会把总复杂度悄悄抬升一个数量级。比如在递归里每层都切片整体就变成了O(n^2)。解决改成传索引下标而不是传切片。递归函数增加start和end参数需要子数组时直接操作原数组的区间避免复制。如果确实需要拷贝优先用arr[start:end]明确切片范围而不是无脑arr[1:]。查这类问题我一般先在本地用timeit跑大数据集看耗时变化趋势再定位到具体是哪个操作拖慢了速度。5.4 边界条件漏判空输入与极端值现象提交后只挂了某一个用例报错信息是IndexError或者返回了完全错误的结果而这个用例往往是空输入或者包含极大数值的输入。原因写代码时盯着主流程忽略了边界分支。比如链表题没处理头节点为空数组题没考虑长度为 0 或者只有 1 个元素数值题没考虑负数或 0 的除法。解决我给自己定了一条规矩写完主逻辑立刻写边界守卫。函数入口先处理最小输入if not nums、if not head、if n 1这些守卫语句永远放在最前面。LeetCode 的题目通常在描述里会标注数据范围读题时圈出范围代码里对范围内的极值都要过一遍。我把这套题的边界用例集合做成了一个固定清单每道题跑三遍空输入、单元素、超大值全部通过才提交。5.5 照搬题解不复盘看懂了不等于会写现象照着question.md的代码敲了一遍感觉全懂了过了三天重新遇到同类型的题还是写不出来甚至无从下手。原因复制代码是零成本获得反馈的过程但大脑没有参与构建思路。刷题的本质是建立「问题特征 → 算法选择」的条件反射这个反射只能通过自己从空白文件开始写来训练。解决这套题解的每个question.md我都先盖住代码部分只读题目描述和思路分析然后自己动手写。写完之后再对照作者的代码找出差异点——可能他是用字典我用了两层列表可能他用迭代我用了递归。有差异才有收获。我给自己定的目标是每题至少独立写两遍第二次在一周后重刷时进行能顺畅写出来才算真正吸收。6. 验证进阶用 pytest 给题解建回归测试再按主题做两轮重刷6.1 把题解变成可回归的测试资产光有代码没有测试刷过的题很快就会变成黑匣子——你知道它当时通过了但不知道它现在是否还通过。我的做法是给每道题在本地建一个对应的测试文件用pytest管理把题目附带的示例和自己的边界用例全部固化下来。以 994 为例测试文件长这样import pytest from solutions.p0994 import orangesRotting def test_normal_case(): grid [[2, 1, 1], [1, 1, 0], [0, 1, 1]] assert orangesRotting(grid) 4 def test_no_fresh(): grid [[0, 2]] assert orangesRotting(grid) 0 def test_impossible(): grid [[2, 1, 1], [0, 1, 1], [1, 0, 1]] assert orangesRotting(grid) -1test_开头的函数会被pytest自动收集每个函数里的assert是断言语句条件为假时测试就失败并输出当时的实际值。运行pytest p0994_test.py -v可以看到每个用例的通过状态。这样做的价值在于未来你换了一种思路重写这道题或者 Python 版本升级带来行为变化运行一次测试就能立刻发现回归问题。6.2 按主题建刷题索引两周一轮的复习节奏题目刷太多之后按题号顺序重刷效率很低我按「数据结构 算法范式」给这套题解建了一个索引文件把每道题的编号标记为主题标签比如二叉树、动态规划_背包、二分答案、BFS_网格。复习时按标签批量重做每次一个主题。第一次刷是学解法第二次刷是练速度第三次刷是口头讲清思路。从我的血泪经验看能不看代码把一道题的思路完整讲给别人听才是真的过关。我给自己定的节奏是两周一轮重刷每次只重做「当时卡壳超过半小时」的题和一个随机主题总共控制在 20 道左右。重刷时不用本地测试文件直接在 LeetCode 或本地空文件里写写完再跑测试对比优化。从那以后我每次拿到新题解都先建测试、再开刷不给自己的记忆留黑匣子。希望今天的这些实践经验能让你把这份 Python 版题解刷出比原仓库更扎实的效果。本文还有配套的精品资源点击获取
返回列表