ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python3力扣刷题实战:环境配置、高效刷题法与核心库技巧

Python3力扣刷题实战:环境配置、高效刷题法与核心库技巧 这是一篇偏实操向的刷题记录我把这几个月用 Python3 在力扣上刷题踩过的坑、整理过的方法、还有今天整理出的一套“自用刷题框架”都写出来了。内容比较多但每一段都是我自己遇到真问题后总结下来的不是那种泛泛的攻略。如果你也正处在“知道要刷题但不知道从哪下手”的阶段这篇应该能帮你省不少时间。1. 刷题前的环境自检先让本地跑起来再说很多人打开力扣网页就开始做题做完直接提交本地环境根本不管。我一开始也这样直到有一次需要调试一个递归栈溢出的问题才发现本地连个像样的 Python3 环境都没有配好。所以如果你打算长期刷建议还是花二十分钟把本地环境理顺。1.1 版本选择别用太老的 Python3力扣的在线判题环境现在一般支持到 Python 3.10 左右本地开发我建议直接用 3.10 或 3.11原因很简单类型注解的写法更舒服match语句在 3.10 以后可用而且dict、list这些内置容器的运行效率也有优化。如果还在用 3.6 或 3.7倒不是说不能刷题只是有些力扣官方题解里用到的新语法特性你本地跑不了还得改代码很影响心情。安装方式看系统来。macOS 上我推荐直接用 Homebrewbrew install python3.11装完记得看一眼python3 --version确认一下。Windows 的话去官网下载安装包勾选“Add Python to PATH”那个选项不然命令行里敲python会没反应。Linux 发行版大多自带 Python3但版本可能偏旧比如 Ubuntu 20.04 自带 3.8日常用没问题不过如果你想用新特性最好还是自己编译安装一个后面我会单独讲离线安装的情况。1.2 用 venv 隔离项目依赖刷题会用到第三方库吗大部分题目用标准库就够了但也有例外比如调试时你想用rich美化输出或者想用pytest批量跑测试用例。这种时候就千万别把包装到系统 Python 里否则一旦系统升级或者装其他软件依赖冲突能把人搞疯。我的做法是每个刷题项目建一个独立的虚拟环境mkdir leetcode cd leetcode python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pytest richvenv 的好处就是你随便折腾坏了直接删掉重建不会影响系统环境。像pip install的时候也不需要加sudo权限问题少很多。如果你用的是 PyCharm新建项目的时候直接选虚拟环境路径就行图形界面操作更简单。1.3 一个高频坑ensurepip 报错的处理用 venv 的时候有个很常见的报错尤其是某些精简版 Linux 环境或者自己编译的 Pythonerror: command [/opt/driver-monitor/.venv/bin/python3, -m, ensurepip, --upgrade, default-pip] returned non-zero exit status 1这个报错网上搜一下能搜到一大堆核心原因一般是 Python 安装时没带完整的标准工具链或者ensurepip模块损坏。我踩过一次之后学乖了解决方法也简单先看看系统里有没有完整的 standard library如果在/usr/lib/python3.x/ensurepip下找不到文件说明安装时就没带上网上能下对应版本的get-pip.py然后用python3 get-pip.py手动装 pip实在不行直接删掉坏虚拟环境用python3 -m venv --without-pip .venv创建再手动把 pip 装进去。如果你是在内网或者离线环境装 Python3这个报错出现频率会更高。我后来专门整理过一个离线方案直接用系统已有的 pip 下载 wheel 包再拷贝过去安装能绕开很多麻烦。1.4 离线环境怎么装第三方库有些读者可能在隔离网络环境下做开发没法直接pip install。我之前在一台不联网的服务器上部署了一个数据分析脚本需要装numpy和pandas当时就是先用能联网的机器把包下载好再拷贝到目标机器离线安装# 在有网络的机器上执行 pip download numpy pandas -d ./offline_packages # 把 offline_packages 目录拷贝到目标机器后执行 pip install --no-index --find-links./offline_packages numpy pandas注意pip download时最好指定和对方的 Python 版本、操作系统一致的平台否则下载的 wheel 可能装不上。如果是非常小的工具库也可以直接把源码包.tar.gz下载后拷过去pip install /path/to/package.tar.gz就行。2. 刷题顺序怎么定热题100是主线但不能莽力扣上题号从1到3000多你要是顺着题号刷大概率两三天就放弃。最经典的主线是“力扣热题100”但怎么刷也有讲究。2.1 热题100的题目画像热题100是官方根据高频面试题整理出来的但它的难度和类型分布其实是有规律的。我统计了一下自己刷过的记录大致分成三块数据结构基础题数组、字符串、链表、二叉树大概占四成算法思维题动态规划、贪心、回溯、二分查找大概占四成综合模拟题设计类、数学类、脑筋急转弯类大概占两成。所以你在规划时间时不要前两周全刷数组题后面突然跳到一个困难的动态规划心态容易崩。我的建议是按类型分阶段集中刷一次吃透一类题。2.2 具体时间安排参考我当时给自己定的是每天保证两小时大概能完成两到三道题。前两周只做简单题把 Python 的写法手感找回来。第三周开始进入中等题并且按类型分组比如这周只刷“滑动窗口和双指针”下周只刷“二叉树遍历”。这里有一个关键经验不要按提交通过率选简单题而要先看题目本身涉及的核心数据结构是什么。比如“两数之和”虽然通过率高但它背后是哈希表优化问题而某些看起来吓人的“困难”题其实就是“二叉树的最近公共祖先”这种套了个复杂外壳实际上思路很固定。我后来把主刷顺序调整为数组与哈希表熟悉list、dict、set的基本操作和复杂度概念链表快慢指针、反转、合并、环形检测二叉树前中后序遍历递归和迭代都写一遍、层序遍历、最近公共祖先双指针与滑动窗口字符串和数组题的常客回溯与递归排列、组合、子集问题动态规划先入门背包和爬楼梯再上复杂状态转移。这个顺序很像搭积木前面的数据结构是后面算法题的基础。你直接做动态规划如果你的递归和二叉树不熟画状态转移树都会吃力。2.3 每天刷几题合适我之前用过“一天五题”的激进打法结果到了第十天就开始疲惫而且每道题没时间深挖只是看一眼题解抄一遍代码自我感动而已。后来调整成“一天两题一道旧题重做”之后反而稳下来了。关键是给自己留复盘时间。刷题核心不是量而是能否条件反射地把一道新题归类到已经见过的解题模式里。所以每天两题之后我会花半小时把当天题目的解题思路写成两到三行笔记哪怕只有自己能看懂也比闷头刷十题有效。3. python3 刷力扣最常用的标准库和技巧这里整理一下我在刷题过程中使用频率极高的 Python3 标准库方法。很多刚刷题的人觉得 Python 解题慢其实不是语言慢是对库函数不熟把本该一行搞定的操作写成了循环。3.1 collections计数器、默认字典、双端队列collections模块里有三个类我几乎每三天就用一次from collections import Counter, defaultdict, deque # Counter 统计字符频次 cnt Counter(leetcode) print(cnt[e]) # 3 # defaultdict 处理分组统计避免手动判断 key 是否存在 group defaultdict(list) group[a].append(1) # deque 实现队列/双端操作效率远高于 list 的 pop(0) dq deque([1, 2, 3]) dq.appendleft(0) dq.pop()Counter在“有效字母异位词”“字符串排列”这类题目里就是神器。你不需要手动写一个统计函数直接Counter(s1) Counter(s2)就完成判断。deque用于“滑动窗口最大值”一类的题时可以维护一个单调队列比每次都切片取最大值复杂度低了好几个量级。3.2 heapqTopK 和贪心题的常用工具Python 里的heapq默认是小根堆。如果你需要找最大 K 个数通常有两种思路一是把全部数据取反后 push 进堆二是维护一个大小为 K 的小根堆堆顶就是第 K 大的元素。import heapq nums [3, 1, 4, 1, 5, 9, 2, 6] # 找最大的 3 个数 print(heapq.nlargest(3, nums)) # [9, 6, 5] # 手动维护小根堆 heap [] for x in nums: heapq.heappush(heap, x) if len(heap) 3: heapq.heappop(heap) # heap 里是 [4,5,6,9] 中最小的三个[4,5,6]堆在“合并 K 个有序链表”“前 K 个高频元素”这类题目里几乎是标准解法。有些贪心题也可以用堆做“后悔”操作比如“课程表 III”这类题先把课程按截止时间排序再用一个小根堆维护已选课程如果总时长超了就弹出耗时最长的课程。3.3 functools.lru_cache记忆化搜索的快速实现递归剪枝是回溯和动态规划题的基础但很多时候递归重复计算太严重需要记忆化。Python 里不需要手动建字典存状态functools.lru_cache直接帮你完成了。from functools import lru_cache lru_cache(maxsizeNone) def fib(n): if n 1: return n return fib(n-1) fib(n-2) print(fib(50))注意一点lru_cache缓存的键是函数的参数所以如果递归函数里用了不可哈希的参数比如list会直接报错。解决办法是在外层定义普通函数把可变参数转换成元组再传入被装饰的内部函数。另外lru_cache对递归深度也有要求如果递归很深需要配合sys.setrecursionlimit使用。3.4 bisect二分查找不用自己写力扣里很多“查找插入位置”“寻找目标值区间”的题都是二分查找变体。你要是每次都手写left, right 0, len(nums)-1来回折腾边界条件特别容易出错。Python 的bisect模块可以少写至少五行代码import bisect nums [1, 3, 5, 6] print(bisect.bisect_left(nums, 5)) # 2 print(bisect.bisect_right(nums, 5)) # 3bisect_left返回最左边插入位置bisect_right返回最右边插入位置。对于“寻找有序数组中目标值的第一个和最后一个位置”这样的题直接调用两个函数然后判断索引是否越界就行又稳又快。3.5 其他零碎但高频的内置函数enumerate遍历时同时拿到索引和值比for i in range(len(nums))可读性好zip并行遍历多个列表转置二维数组时特别有用sorted(key...)复杂排序力扣题频繁出现比如按区间终点排序或者按字符串长度排序all/any批量判断时避免写出又臭又长的循环。我还喜欢在本地用 Python 的doctest跑示例用例。力扣题目一般都给了输入输出示例我把这些示例当作文档字符串写在函数里然后直接用python -m doctest solution.py跑一遍能很快发现边界错误。4. 调试与报错排查从本地提交到力扣的细节差异这里记录一下我最常遇到的几类问题还有一些排查思路。如果你刷题时经常“本地能跑一提交就错”这节应该能帮到你。4.1 输入输出差异力扣上的核心代码模式其实已经帮我们处理好了输入输出你只需要实现Solution类里的方法。但是很多人习惯先在本地用input()写一段完整程序再移植过来的时候容易把print留在函数里导致提交失败。我的习惯是本地写代码时也直接按照力扣的接口风格来写手搓几个示例调用而不是写input()版本。这样测试用例扩展起来很方便提交前也减少一层“翻译”工作。如果你刷的是 ACM 风格题目必须自己处理读入那就用sys.stdin.readline而不是input()尤其在数据量大的时候input()的缓冲效率要低不少。4.2 最常见的生产级报错排查表下面是基于我实际刷题遇到的高频报错整理的一张速查表错误类型出现场景解决思路IndexError: list index out of range访问了不存在的列表下标检查边界比如空列表、循环结束条件常发生在双指针缩范围时KeyError字典里访问了不存在的键考虑用defaultdict或dict.get(key, default)Time Limit Exceeded算法复杂度过高检查是否有重复循环嵌套尝试用哈希表、双指针或前缀和优化RecursionError递归深度过大用sys.setrecursionlimit(10000)或者改迭代写法AttributeError: NoneType object has no attribute val链表/树节点为空时访问属性判断节点是否为None再做.val访问TypeError: int object is not iterable忘记把数字变成列表或范围常见于把n当range(n)使用ValueError: not enough values to unpack解包时数量不匹配检查赋值左右是否一致例如a,b ...但右边只有一个值4.3 一个实际排查案例举个例子之前刷“无重复字符的最长子串”时我本地写了滑动窗口代码测试示例全都通过一提交就超时。后来发现我为了求窗口长度每次更新时都调用len(set(window))这个操作在每次滑动时都是 O(k) 复杂度整个算法变成了 O(n²)。当时的修复办法是改用一个Counter来维护窗口内各字符的频次并维护一个当前不同字符的独立计数变量这样每次更新就是 O(1) 的整体降到 O(n)。# 错误版本里的高频操作 # window s[left:right] # if len(set(window)) ...: # O(n) 的重复计算 # 修正版本用 Counter 维护窗口 from collections import Counter cnt Counter() distinct 0 for right, ch in enumerate(s): cnt[ch] 1 if cnt[ch] 1: distinct 1 while distinct k: # 以 k 为条件举例 left_ch s[left] cnt[left_ch] - 1 if cnt[left_ch] 0: distinct - 1 left 1这类问题如果没有本地性能分析习惯比较难一眼看出来。我建议在本地用timeit跑一下规模大一点的随机数据如果发现函数执行时间和理论复杂度对不上就去检查内部是不是藏着隐式的 O(n) 操作。4.4 超时和递归溢出的优化参考超时问题有几个常见方向看数据范围如果n达到 10^5 甚至 10^6O(n²) 基本必挂要设计 O(n log n) 或 O(n) 的算法检查有没有不必要的切片操作nums[:]和字符串拼接在某些场景下是 O(n)如果写在循环里就变成 O(n²)尽可能在循环外用局部变量绑定常用的函数或方法比如append res.append虽然提升有限但养成习惯在写复杂嵌套循环时会有帮助。递归溢出方面力扣对 Python3 的递归深度限制其实挺宽松但如果你递归深度到几千层还是会报RecursionError。最稳妥的办法是把递归改成显式栈迭代特别是树的遍历这种场景。5. 自用刷题本我如何整理错题和算法模板笔记和整理是我觉得刷题最有价值的部分。不是说把题解抄一遍就算有了而是要整理出每个题型的“最小可复现代码模板”。5.1 给题目标签而不是只按题号排序我的做法是在本地维护一个 Markdown 文件每道题按topic: 数据结构-算法-难度打标签。比如哈希表-两数之和-简单链表-快慢指针-中等动态规划-背包-中等这样一方面方便后续查找另一方面你会很快发现自己哪个标签下的题刷得最少。统计了一下大部分人容易长期绕开“动态规划”和“图论”这两类如果你也是说明你该刻意补一补了。刷题记录里我还会标记一个“复刷日期”比如今天错得很惨的题我会安排一周后和一个月后各复刷一次。复刷时不是简单重写代码而是先看自己能不能独立写出解题思路再动手。5.2 算法模板的收集方式模板不在多在于“能应对变形”。我常用的几个模板如下。二叉树迭代式前序遍历用显式栈stack [root] res [] while stack: node stack.pop() if node: res.append(node.val) stack.append(node.right) stack.append(node.left)回溯模板处理排列、组合、子集等def backtrack(path, choices): if is_selected(path): res.append(path[:]) return for i, choice in enumerate(choices): if is_valid(choice): path.append(choice) backtrack(path, choices[:i] choices[i1:]) # 依据场景调整 path.pop()二分查找最稳的循环写法适用于绝大多数题lo, hi 0, len(nums) - 1 while lo hi: mid (lo hi) // 2 if nums[mid] target: lo mid 1 elif nums[mid] target: hi mid - 1 else: # 根据题目要求决定向哪边收缩做题时如果用的模板没通过某个用例我会在模板旁边备注一行“此路不通的场景”积累多了就形成自己的“避坑手册”了。5.3 清理旧题的两种方式我通常不删旧的解题代码但隔一段时间会重新写一遍然后对比两个版本的差异。如果新版本代码行数更少或者命名更清晰说明这里真的掌握得不错了。另外还有一种“只看不做”的复习方式周末把这一周刷过的题改成面试问答模式每道题只在大脑里过解题思路不打开编辑器。这个过程能把“看得懂”变成“想得起”效果比闷头再刷一遍好很多。6. 力扣刷题热的背后该不该全量刷完聊一点题外话。平台上的题有两千多道正常人不可能也没必要全刷完。所谓“力扣热题100”之所以流行是因为它把所有常见面试考点都浓缩进了一个可执行清单。我的经验是先吃透这100题再根据面试岗位补充对应专题比追求“题量上千”要高效得多。对于 Pythoner 来说刷题还有一个隐性收益你会对语言的内置数据结构有更深刻的理解。比如list内存连续增长和deque的分块存储有何不同dict的底层哈希冲突怎么影响查询效率这些在写业务代码时不一定会深入但刷题时会被迫面对反哺到工程里是实打实的提升。如果你刚入门建议先不要管“别人一天刷了多少题”那种帖子每个人的基础不一样工作时长不一样适合自己的节奏才是最好的。我的最终心得就是保持每天都碰两道题的节奏或许某一天打开力扣时你会突然发现那些以前看不懂的题解现在读起来豁然开朗。那个时刻就是量变到质变的信号。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进