尧图精选

Python算法题基础操作速查:从输入输出到标准库的实战指南

🕒 发布时间:2026/9/17 4:41:43 📁 来源:尧图网络
刷算法题这事跟做菜有点像菜谱思路谁都能背真正拉开差距的是刀工——也就是把“我想到了”变成“我写出来了”的那几十秒。Python 写算法题最大的优势是语法糖多、标准库能省一大堆代码但前提是你得把这些高频基础操作练到闭着眼都能打出来。否则考场上思路想通了结果卡在切片边界、输入格式化、字典排序这种地方特别亏。这篇就是给准备笔试、面试的朋友做的一份“Python 算法题基础操作速查”。我把这两年刷题、面试、帮人改代码时最常碰到的操作按场景整理出来不扯底层原理只讲怎么用、为什么这么用、有哪些坑。适合两种人看一种是刚入门想系统过一遍基础操作的另一种是刷题有一阵子但总在细节上翻车想查漏补缺的。内容不追求大而全但保证每一段都是在真实题目里用得上的。1. 输入输出算法题的第一道坎也是最容易被忽略的1.1 别再用 input() 逐行读了sys.stdin 才是考场主力很多人在本地 IDE 里习惯了input()到了笔试平台才发现数据量一大就卡。原因很简单input()底层也是调标准输入但它每次读取、strip、转类型全都混在一起数据量到百万级别时性能差距就很明显了。刷题圈里约定俗成的做法是import sys data sys.stdin.read().split()这一行能干很多事。sys.stdin.read()把整个标准输入一次性读进来split()按空白字符空格、换行、Tab切分成字符串列表。这样无论输入是换行分隔还是空格分隔你都能拿到一个扁平的列表再按顺序取就行。我自己的习惯是先拿一行测试样例过一遍确认格式以后立刻换成这种read().split()的读法。特别是多组测试数据、二维数组这种场景省心得多。1.2 输出别硬拼 printjoin 是性能救星print()每次调用都有 I/O 开销。如果你在一个循环里打印几万次时间会肉眼可见地变慢。笔试场景里输出结果通常是一组数或一行字符串标准做法是先收集再一次性输出ans [] for x in result: ans.append(str(x)) print( .join(ans))这里有个细节join只接受字符串序列所以数字要先用str()转一下。很多人第一次写就挂在这报错TypeError: sequence item 0: expected str instance, int found其实就是在join前忘了转类型。还有一个高频场景输出一个二维数组的每一行for row in matrix: print( .join(map(str, row)))map(str, row)把整行数字转成字符串再去join一行代码搞定而且比在循环里反复print(x, end )干净得多。1.3 多组数据的读取套路很多题目长这样“第一行一个整数 T表示测试数据组数接下来 T 组输入。” 这种题如果老老实实循环input()逻辑上没错但代码会很啰嗦。我一般这么处理import sys data sys.stdin.read().split() p 0 T int(data[p]) p 1 for _ in range(T): n int(data[p]) p 1 arr list(map(int, data[p:p n])) p n # 处理 arr关键是用一个游标p手动维护读取位置。这样不管输入格式多复杂都能用同一套代码应付。遇到那种输入结尾不告诉你组数、读到 EOF 为止的题就把T int(data[p])这层去掉直接循环里判断边界。提示笔试平台很多推荐用sys.stdin.readline配合while True循环读直到读到空字符串。这两种风格没有绝对对错但read().split()在处理“未知行数、未知格式”时更省心推荐优先掌握它。2. 内置数据结构这些操作得形成肌肉记忆2.1 list 的高频操作切片、反转、去重、二维数组Python 的列表操作里切片是算法题里出现频率最高的一个。它的语法是list[start:end:step]左闭右开也就是a[1:3]取的是下标 1 和 2不包含 3。这个边界规则是新手翻车的重灾区尤其是二维数组遍历、滑动窗口、子数组问题时边界写错一题就废了。几个高频切片写法a[::-1] # 反转整个列表 a[::2] # 取偶数下标 a[1:] # 去掉第一个元素 a[:-1] # 去掉最后一个元素 a[:k] a[k:] # 基本等于没切但能用来做旋转去重的最简写法是list(set(a))。但要注意set会打乱顺序如果你需要保留原顺序用dict.fromkeys(a)a [3, 1, 2, 3, 1] b list(dict.fromkeys(a)) # [3, 1, 2]这个技巧在需要去重且保持顺序的题里特别好用缺点是代码可读性差一点但面试时能写出来说明你懂 dict 的有序性Python 3.7 起 dict 保证插入顺序。二维数组的创建也是一个经典坑# 错误写法 matrix [[0] * 5] * 3 # 3 行指向同一个列表 # 正确写法 matrix [[0] * 5 for _ in range(3)][[0] * 5] * 3看起来没问题实际上三个子列表是同一个对象的引用改一个全变。这种问题在 LeetCode 上见过太多次了尤其涉及到初始化二维 DP 表的时候。2.2 dictget、setdefault、排序是三大高频需求字典在算法题里基本就是“计数器”和“查找表”两种角色。作为计数器最朴素的写法是counter {} for x in arr: counter[x] counter.get(x, 0) 1get方法第二个参数是默认值键不存在时返回 0省去if x not in counter的判断。如果统计的是字符可以直接用collections.Counter后面章节会讲。作为查找表setdefault的实用性被很多人低估。它的作用是键不存在时设置默认值存在时返回原值。经典场景是给一组数据分组groups {} for item in items: key item[0] groups.setdefault(key, []).append(item[1])没有setdefault的话你得写先if key not in groups: groups[key] []然后groups[key].append(...)两行变一行代码密度高不少。字典按值排序也是高频操作sorted_dict sorted(d.items(), keylambda x: x[1], reverseTrue)这里d.items()返回键值对keylambda x: x[1]表示按值排序。想要先按值降序、再按键升序的话sorted_dict sorted(d.items(), keylambda x: (-x[1], x[0]))负号只能用于数值如果值是字符串就得用reverse参数做多级排序这个点很多面试官喜欢提问。2.3 set集合运算比你想的好用set除了去重最容易被忽视的是它内置的集合运算交集、并集|、差集-。这在“找两个列表公共元素”、“判断子集”、“统计出现过的所有字符”这类题目里非常方便a set([1, 2, 3]) b set([2, 3, 4]) a b # {2, 3} a | b # {1, 2, 3, 4} a - b # {1}判断一个列表是否包含重复元素一行搞定has_dup len(arr) ! len(set(arr))in操作在set里是 O(1) 平均复杂度在list里是 O(n)。如果代码里涉及频繁的“元素是否出现过”判断永远优先把数据放进set。这道题我刷过很多次发现很多人明明想到了用集合却还拿list调用in白白多了几倍的耗时。3. 几个给算法题特别加分的标准库3.1 heapq堆在算法题里不要太实用heapq是 Python 标准库中最被低估的模块之一。优先队列、TopK、合并多个有序列表、Dijkstra 最短路全都能用到它。基础操作只有四个背下来就够用import heapq heap [] heapq.heappush(heap, 3) # 入堆 heapq.heappush(heap, 1) heapq.heappush(heap, 2) smallest heapq.heappop(heap) # 弹出最小值 1Python 的堆默认是最小堆。如果你想用最大堆一个小技巧是存负数heapq.heappush(heap, -x) max_val -heapq.heappop(heap)取前 K 个最大元素最省心的工具是heapq.nlargest和heapq.nsmallesttop3 heapq.nlargest(3, [1, 5, 2, 8, 3])面试时如果要求手写 TopK一般要求你用堆实现 O(n log k) 的复杂度而 Python 的nlargest底层会做优化当 k 接近 n 时用排序否则用堆所以直接用它其实是效率很高的写法。但要注意有些面试官会预设立场认为你“投机取巧”所以能解释清原理再调用比只会调 API 要好。3.2 bisect二分查找的标准答案手写二分查找很容易出边界错误但bisect模块把这块封装好了。你只需要记住两个函数import bisect a [1, 3, 5, 5, 7] bisect.bisect_left(a, 5) # 2最左侧插入位置 bisect.bisect_right(a, 5) # 4最右侧插入位置bisect_left返回“第一个 x 的位置”bisect_right返回“第一个 x 的位置”。这个区别在处理重复元素时极其重要统计有序数组中x出现的次数bisect_right(a, x) - bisect_left(a, x)查找“第一个大于 x 的元素”bisect_right(a, x)如果等于len(a)说明不存在在有序列表中插入且保持有序bisect.insort(a, x)bisect_left还有一个妙用在“枚举所有子集的和”这类题里配合前缀和做配对。我刷题时一般会先把bisect_left和bisect_right的边界规则写在一张便签上考试前瞄一眼。3.3 itertools排列组合与轮询神器itertools里的permutations、combinations、product是解组合类问题的利器。很多暴力搜索题手写 DFS 也是那么回事但直接调用库能省下大量时间from itertools import permutations, combinations, product list(permutations([1, 2, 3], 2)) # 排列有序 list(combinations([1, 2, 3], 2)) # 组合无序 list(product([0, 1], repeat3)) # 笛卡尔积相当于三个位置的 0/1 枚举product在“二进制枚举”里很常用。比如枚举一个长度为 n 的数组的所有子集时product([0, 1], repeatn)配合列表推导式可以一行生成所有子集掩码。不过数据量超过 15 时就别用这个了2^n 会爆炸。itertools还有一个不太起眼但很实用的accumulate用来算前缀和from itertools import accumulate prefix list(accumulate([1, 2, 3, 4])) # [1, 3, 6, 10]面试手写前缀和当然不难但用accumulate能减少临时变量代码也更不容易出错。配合zip可以做差分数组。3.4 collectionsdeque、Counter、defaultdict 三件套collections里的deque是双向队列BFS 的标配。为什么 BFS 用deque而不是list因为list.pop(0)是 O(n) 操作而deque.popleft()是 O(1)。之前我遇到过用list模拟队列导致超时的题目换成deque瞬间 AC。from collections import deque q deque([start]) while q: node q.popleft() for nxt in neighbors(node): q.append(nxt)Counter是计数器的高级封装from collections import Counter c Counter(abracadabra) c[a] # 5 c.most_common(2) # [(a, 5), (b, 2)]most_common在“出现频率最高的元素”类题目中是一句话的事。Counter之间还可以做加减运算、交集并集这在比较两个字符串的字符构成时很好用。defaultdict解决“键不存在就自动初始化”的问题from collections import defaultdict graph defaultdict(list) graph[1].append(2) # 不需要先 graph[1] []建图、建邻接表、按字段分组用defaultdict(list)或defaultdict(int)几乎是最优解。它本质上就是dict.setdefault的语法糖但可读性要好得多。4. 字符串处理算法题里的隐藏大 boss4.1 split 与 strip 的边界细节字符串处理题看起来简单实际上坑特别多。第一个高频坑是split()与split( )的区别。不带参数的split()会按任意空白字符切分并且自动过滤连续空白和首尾空白split( )只按单个空格切a b.split( )会得到[a, , b]。如果输入是整行读进来的想要把它切成词列表用split()就好别加参数。s1 hello strip()去掉首尾空白lstrip()去掉左边rstrip()去掉右边。记住一个常见场景读进来的行末尾有换行符\nstrip()会把它一起去掉。但如果字符串中间有你需要保留的空格别用strip()处理否则两边会被误删。4.2 join 别用 来替代字符串拼接很多人习惯用在算法题里这不是最优选择。Python 字符串是不可变对象每次都会生成新字符串循环里拼接是 O(n^2) 复杂度。正确做法是收集到列表后用join# 不推荐 s for ch in chars: s ch # 推荐 s .join(chars)这个点面试时会作为“Python 性能优化”的常见问题出现实际刷题时文本处理量没那么大但形成好习惯没坏处。4.3 常用字符判断与转换判断字符类型Python 提供了isalpha()、isdigit()、isalnum()、islower()、isupper()。注意isdigit()对²这种上标数字也会返回True如果要严格判断 0-9用ch.isdecimal()或直接0 ch 9。大小写转换是lower()/upper()首字母大写是title()这在处理拼写检查类题目里很常用。replace替换所有子串find返回子串第一次出现的下标不存在返回 -1count统计子串出现次数。这些 API 看着基础但在字符串模拟题里是主力。4.4 字符串转列表与列表转回字符串s hello chars list(s) # [h, e, l, l, o] s2 .join(chars) # 转回字符串 rev s[::-1] # 字符串反转字符串反转s[::-1]是我见过最高频的切片用法回文判断、反转单词、大数加法里都用得到。要按单词反转先split()再反转再joins the sky is blue rev_words .join(s.split()[::-1]) # blue is sky the5. 排序与二分两个影响通过率的核心模板5.1 sorted 的 key 参数是怎么玩的排序几乎是算法题必备能力。Python 的sorted()和list.sort()都接受key参数key是一个函数返回值作为排序依据。它不会改变元素本身只是拿返回值做比较。掌握这个以后各种花式排序都能一行写完items.sort(keylambda x: x[1]) # 按元组第二个元素排序 items.sort(keylambda x: len(x)) # 按长度排序 items.sort(keylambda x: (-x[1], x[0])) # 先按第二个元素降序再按第一个升序 arr.sort(keystr) # 按字符串字典序用于数字拼接比较“把数组排成最小的数”这道经典题核心就是自定义排序规则from functools import cmp_to_key arr [3, 30, 34, 5, 9] arr.sort(keycmp_to_key(lambda a, b: int(a b) - int(b a))) result .join(arr)cmp_to_key把旧的比较函数转换成key函数这个转化在 Python 3 里是兼容旧代码的关键。面试时如果被问到这种题能写出自定义比较规则比背答案强得多。5.2 一个不容易写错的二分模板就算是工作好几年的工程师手写二分也可能卡在边界条件。我推荐一个比较不容易写错的模板结合bisect模块的原则def lower_bound(arr, target): left, right 0, len(arr) while left right: mid (left right) // 2 if arr[mid] target: left mid 1 else: right mid return left这个模板的含义是返回第一个大于等于target的位置。循环条件是left rightright初始化为len(arr)而不是len(arr) - 1这样当目标值大于所有元素时返回len(arr)语义清晰。如果你需要第一个大于target的位置把arr[mid] target换成arr[mid] target即可。这段模板跟bisect_left/bisect_right的行为一致笔试时可以直接用bisect替代但面试手写时用模板更稳妥。5.3 lambda 与排序结合的注意事项lambda表达式虽然好用但有两点要注意第一lambda里不能写复杂语句只能写单个表达式。如果要写逻辑分支建议定义普通函数def sort_key(x): if x[1] 0: return (0, x[0]) return (1, x[0])第二key函数会被调用多次如果里面做了复杂计算比如递归、大字符串拼接性能会受影响。这种时候应该先预处理把计算结果存起来再排序data [(s, sort_key(s)) for s in strings] data.sort(keylambda x: x[1])面试中考察排序时常常是为了看你能否用贪心思路设计出正确的比较规则所以 key 函数的设计比排序本身更重要。6. 数学运算与常用算法组合6.1 取模、幂运算、最大公约数算法题里经常碰到大数运算Python 的整数不限制位数这点特别好但也带来了性能问题。取模运算pow(a, b, mod)是快速幂的内置实现底层的复杂度是 O(log b)手写快速幂通常没它快result pow(2, 10, 1000) # 计算 2^10 mod 1000结果是 24math.gcd求最大公约数Python 3.9 之后math.lcm求最小公倍数import math math.gcd(12, 18) # 6 math.lcm(4, 6) # 12如果在线评测环境是 3.8 及以下math.lcm会报错只能自己用a * b // math.gcd(a, b)实现。位运算里x 1判断奇偶、x 1相当于整除 2、x 1相当于乘 2、x ^ y异或可以用来交换两个数或找出现奇数次的元素。异或找唯一出现奇数次的元素是经典题ans 0 for x in arr: ans ^ x # ans 就是那个出现奇数次的元素6.2 BFS/DFS 模板不要每次现想BFS 和 DFS 是面试题的大头这两套模板最好背熟。BFS 的骨架在 3.4 节已经给了这里补一个带层数的版本from collections import deque def bfs(start, target): q deque([start]) visited {start} steps 0 while q: for _ in range(len(q)): node q.popleft() if node target: return steps for nxt in get_neighbors(node): if nxt not in visited: visited.add(nxt) q.append(nxt) steps 1 return -1for _ in range(len(q))这个写法是“按层遍历”的关键它保证每轮处理的是同一层的所有节点。很多迷宫题、开锁题、单词接龙题都是这个模板的变体。DFS 的递归模板更简单def dfs(node, visited): if node is None: return visited.add(node) for nxt in node.neighbors: if nxt not in visited: dfs(nxt, visited)要注意 Python 的默认递归深度是 1000一旦递归过深会报RecursionError。遇到需要在很深的树上 DFS 的题要么把递归深度调大import sys sys.setrecursionlimit(1000000)要么直接改成栈 迭代的写法。我在很多笔试题里都吃过这个亏树深度一超过 1000 就崩加一行setrecursionlimit就稳了。6.3 前缀和、差分、双指针的基础组合前缀和用itertools.accumulate可以秒出但要注意下标偏移。经典公式是sum[i:j] prefix[j] - prefix[i]其中prefix比原数组多一位prefix[0] 0arr [1, 2, 3, 4] prefix [0] for x in arr: prefix.append(prefix[-1] x) # arr[1:3] 2 3 5 prefix[3] - prefix[1]差分数组适合区间批量加减的场景比如“多次给某个区间加一个数”。做法是构造一个diff数组区间[l, r]加val时只改diff[l] val; diff[r1] - val最后前缀和还原。这个技巧在数组操作题里非常高频而且大部分题解的第一反应不是区间更新而是暴力循环掌握差分就能超越一大票人。双指针就更常用了。快慢指针找环、左右指针做有序数组两数之和、滑动窗口求最长无重复子串都是算法题的常客。Python 里双指针的代码量不大但需要注意边界条件例如滑动窗口的右指针先扩展左指针再收缩循环里先处理结果再移动指针顺序错了答案就会偏。7. 面试笔试高频翻车点这几个坑我帮大家踩过了7.1 可变默认参数——面试官最喜欢挖的坑def add_item(item, data[]): data.append(item) return data第一次调用add_item(1)返回[1]第二次调用add_item(2)返回[1, 2]。因为默认参数data[]只在定义函数时创建一次后续调用共享同一个列表。这在刷题时偶尔会碰上面试时几乎是必考题。正确写法是用None做默认值函数内部初始化。7.2 浅拷贝 vs 深拷贝二维数组尤其要小心前面提过[[0] * 5] * 3的坑本质就是浅拷贝。copy.copy()只拷贝最外层嵌套列表还是共享的copy.deepcopy()递归拷贝所有层。算法题里一般不要用deepcopy因为它慢且可能爆内存更好的做法是理解你的数据结构用列表推导式创建新的嵌套列表new_matrix [row[:] for row in old_matrix]row[:]是浅拷贝一行但如果行里的元素是可变对象比如列表那还是共享的。需要根据实际情况决定。7.3 for 循环里改列表结果会“超出预期”如果遍历一个列表的同时删除或添加元素很容易跳过元素或导致死循环。经典场景arr [1, 2, 2, 3, 4] for x in arr: if x 2: arr.remove(x)遍历过程中数组长度变化for按下标推进可能漏掉一个2。推荐做法先收集要删除的元素再一次处理或创建一个新列表保留需要的元素arr [x for x in arr if x ! 2]同理在遍历dict时删除键会直接报RuntimeError: dictionary changed size during iteration。遇到这种需求先list(d.keys())再遍历副本。7.4 in 操作的性能陷阱x in list是 O(n)x in set是 O(1)。这个区别我在前面提过但值得再强调一次。如果代码里在循环中反复判断成员关系用列表会导致整体复杂度多一个数量级# 慢 for x in arr: if x in visited_list: ... # 快 visited_set set(visited_list) for x in arr: if x in visited_set: ...笔试平台对时间的要求通常很严格这种性能差距会直接造成 TLE。所以凡是涉及“之前是否出现过”的判断第一反应是把数据放进set。7.5 递归深度限制前面在 DFS 部分说过Python 默认递归深度是 1000。很多在深树上递归的题本地没问题一到线上就RecursionError。这个是所有 Python 刷题人都会遇到的坑解决方式有两种一是加sys.setrecursionlimit(1000000)把限制调大。二是改成迭代写法。需要说明的是即使调大递归限制Python 的递归调用开销还是比循环大如果题目明确指出数据范围很大优先考虑迭代。我在实际笔试中处理“二叉树的最大深度”这类题时通常会同时准备递归和迭代两个版本根据数据规模临时选一个。最后分享一点自己的心得刷题到这个阶段我有一个很深的体会Python 算法题拼的不是“会不会这门语言”而是“在高压环境下能不能快速调用正确的工具”。像是Counter、heapq、bisect、defaultdict这些库用熟了以后很多原本要写几十行的逻辑几句话就讲清楚了代码短了出错的可能性自然就低。另外建议大家在刷题前先花 10 分钟把今天整理的这些基础操作自己在编辑器里打一遍。别复制手打。这个过程能帮你的手指形成记忆比光看不练有用得多。有了这套“肌肉记忆”考试的时候你就不用再想“这个 API 语法是什么样的”可以把全部脑力留给真正的算法思路。如果你在刷题过程中也遇到过让我意料之外的坑欢迎在评论区补充分享。面试题千变万化但基础操作永远是那几板斧练到位了心里就有底。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →