Python算法竞赛常用技巧与内置库详解
本文系统梳理Python在算法竞赛与刷题中的常用技巧。涵盖高效输入输出处理、列表与字符串操作、collections模块(deque、Counter、defaultdict)的高级用法、自定义排序逻辑以及itertools排列组合工具,并深入解析可变与不可变类型传参、列表遍历删除及字典安全访问等易错点,帮助开发者提升代码效率与健壮性。
Python算法刷题背景与引言
近年来,Python因其简洁易读的语法迅速走红,已成为众多程序员的首选语言。许多开发者开始将Python作为第一编程语言进行算法题目的练习与求解。
在近期使用Python进行算法训练时,笔者试图寻找类似C++ STL文档那样系统化的Python刷题常用库API与技巧指南,但未果。因此,结合个人实战经验与网络资料,整理出这份文档,供自己回顾及广大同行参考查阅。
输入输出处理技巧
1. 输入与输出处理
1.1 标准输入格式:第一行通常包含两个整数n和m,以空格分隔。其中n表示后续输入的行数,m表示每行包含的数字个数,数字之间同样以空格分隔。
解决方案:Python的input()函数默认接收字符串类型数据。通过字符串分割、强制类型转换以及列表生成器,可以高效解决此类输入问题。具体实现代码如下:
# 接收两个值,使用n,m分别接收列表中的两个值
n, m = [int(x) for x in input().split()]
# 构造输入列表的列表
num_list = []
for i in range(n):
# python可以不用在意m的值,将所有数值接收进来然后使用len判断长度
tmp_list = [int(x) for x in input().split()]
num_list.append(tmp_list)
同理,若数据间以逗号(,)分隔,只需在split()函数中传入逗号即可实现相同效果。
1.2 单行数字输出
由于Python的print()函数默认以换行符作为结束符,若需在同一行输出数字并以空格间隔,需调整end参数。具体代码如下:
for i in range(10):
print(i, end=' ')
end是print()函数的一个参数,用于指定输出结束时的字符。此处将其修改为空格,即可实现一行数字以空格分隔的输出效果,其他字符可根据需求自行替换。
列表生成、字符串处理与遍历
2. 空列表生成、字符串修改与列表遍历
2.1 代码编写中,常需生成具有特定长度且带有初始值的空列表,生成方式如下:
# 1. 用乘法生成一个初始值为False的长度为100的一维列表
visited = [False] * 100
# 2. 利用列表生成器生成一个n*m的二维的初始值为0的列表
visited = [[0 for i in range(m)] for j in range(n)]
2.2 在Python中,字符串是不可变对象,无法原地修改。若每次修改都生成新字符串,当修改次数频繁且字符串较长时,开销巨大。因此,通常做法是将字符串转换为列表进行修改,最后再拼接回字符串。
string = 'I love to eat chicken'
# 将字符串转换成列表
string_list = list(string)
# .......对字符串列表进行修改
# Code
# 将字符串列表重新拼接成字符串
#string = ''.join(string_list)
2.3 Python中列表遍历有多种方式。最直接的是直接迭代列表元素,但由于算法题常需基于索引操作数组并修改值,更推荐以下代码中的后两种方法:
num_list = [i for i in range(10)]
# 1. 直接迭代列表
for item in num_list:
# Code
pass
# 2. 通过索引进行迭代
for i in range(len(num_list)):
print(num_list[i])
# 3. 通过enumerate函数进行迭代
for index, value in enumerate(num_list):
# index为当前元素的索引,value为当前元素的值
print(index, value)
collections库核心组件详解
3. collections库的高级应用
3.1 deque双端队列
deque是Python中的队列实现(FIFO,先进先出),在队首执行弹出操作时,其性能优于普通list。
特别是在使用BFS(广度优先搜索)算法时,队列是核心数据结构。部分deque使用示例代码如下:
from collections import deque
# 初始化一个最大长度为3的队列
d = deque([1,2,3], maxlen=3)
# 因为初始化队列最大长度为3,再添加元素会把队头元素挤出
d.append(4)
# 初始化一个不限制长度的队列
d = deque()
# 添加元素到队尾部
d.append(1)
d.append(2)
d.append(3)
# 将队首的元素弹出返回
print(d.popleft())
# 弹出队尾元素并返回值
print(d.pop())
# 在队首插入元素
d.appendleft(0)
3.2 Counter计数器
Counter是一个计数器类,可用于统计序列中各元素出现的频次。
以下是具体示例代码:
import collections
# 一共有三种初始方法
# 1. 传入一个序列
print(collections.Counter(['a', 'b', 'c', 'a', 'b', 'b']))
# 2.传入一个字典
print(collections.Counter({'a':2, 'b':3, 'c':1}))
# 3.直接利用=传参
print(collections.Counter(a=2, b=3, c=1))
# 也可以无参数构造,利用update函数更新
c = collections.Counter()
print('Initial :', c)
# Initial: Counter()
c.update('abcdaab')
print('Sequence:', c)
# Sequence: Counter({'a': 3, 'b': 2, 'c': 1, 'd': 1})
c.update({'a':1, 'd':5})
print('Dict:', c)
# Dict: Counter({'d': 6, 'a': 4, 'b': 2, 'c': 1})
# 可以通过访问字典的访问方式访问Counter对象
for letter in 'abcde':
print('%s : %d' % (letter, c[letter]))
# elements()方法可以返回一个包含所有Counter数据的迭代器
c = collections.Counter('extremely')
c['z'] = 0
print(list(c.elements()))
# ['e', 'e', 'e', 'm', 'l', 'r', 't', 'y', 'x']
# most_common()返回前n个最多的数据
c=collections.Counter('aassdddffff')
for letter, count in c.most_common(2):
print('%s: %d' % (letter, count))
# f: 4
# d: 3
# Counter对象可以进行加减交并,直接使用运算符 +、-、&、|
# +会将两个字典中相同字符的出现次数相加,-会给出第一个Counter相对于第二个的差集。交集给出两个计数器当中都有的元素,且计数被赋值为较小的那个,并集为两个计数器的元素出现最多的那个。
c1 = collections.Counter(['a', 'b', 'c', 'a', 'b', 'b'])
c2 = collections.Counter('alphabet')
print ('C1:', c1)
print ('C2:', c2)
print ('\nCombined counts:')
print (c1 + c2)
print ('\nSubtraction:')
print (c1 - c2)
print ('\nIntersection (taking positive minimums):')
print (c1 & c2)
print ('\nUnion (taking maximums):')
print (c1 | c2)
# 以下为输出:
C1: Counter({'b': 3, 'a': 2, 'c': 1})
C2: Counter({'a': 2, 'l': 1, 'p': 1, 'h': 1, 'b': 1, 'e': 1, 't': 1})
Combined counts:
Counter({'a': 4, 'b': 4, 'c': 1, 'l': 1, 'p': 1, 'h': 1, 'e': 1, 't': 1})
Subtraction:
Counter({'b': 2, 'c': 1})
Intersection (taking positive minimums):
Counter({'a': 2, 'b': 1})
Union (taking maximums):
Counter({'b': 3, 'a': 2, 'c': 1, 'l': 1, 'p': 1, 'h': 1, 'e': 1, 't': 1})
3.3 defaultdict——带默认值的字典
常规创建的dict字典不含默认值,若访问不存在的key(如dct[a]),程序将抛出错误。
在算法与数据结构设计中,往往希望任意给定key都能从字典中获取值(即使只是默认值)。此时,需使用defaultdict来实现这一功能。
例如,在构建图的邻接表表示时,希望以节点为key,相连节点列表为value。此时可使用defaultdict(list)创建默认值为空列表的字典,简化代码逻辑。
# list的默认值为空列表
list_dict = collections.defaultdict(list)
# int的默认值为0
int_dict = collections.defaultdict(int)
print(list_dict['a'])
print(int_dict['a'])
# 输出:[]
# 输出:0
3.4 小结
collections库中,常用于算法与数据结构实现的即为上述几种。其他如排序字典(OrderedDict)和命名元组(namedtuple)在常规算法题中较少使用。
列表与字典排序策略
4. 排序技巧
4.1 列表排序
Python列表排序主要有两种方法:一是使用列表内置的sort()函数,该函数直接在原列表上进行修改,无返回值,但可通过key参数自定义比较键或比较函数。
二是使用内置函数sorted(),该函数自由度更高,可自定义比较函数和key,并返回一个新的排序后列表。
若需自定义比较函数,需从functools库导入cmp_to_key函数,将比较函数转换为key参数。使用代码如下:
def custom_sort(x,y):
if x>y:
# 返回-1代表需要排在前面
return -1
if x<y:
# 返回1代表需要排在后面
return 1
return 0
lst = [i for i in range(10, -1, -1)]
print(lst)
lst.sort()
print(lst)
print(sorted(lst, key=cmp_to_key(custom_sort)))
# 输出如下:
# [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
4.2 字典/元组列表排序
若需对字典(通过item()函数转化为元组列表)或每个元素包含两个值的元组列表进行排序,需利用sorted()函数的key参数指定排序依据的值。代码如下:
# 利用字符串创建计数器字典
d = dict(collections.Counter('Hello World'))
print(d)
# 排序
print(sorted(d.items(), key=lambda x: x[1], reverse=True))
# 输出如下:
# {'H': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'W': 1, 'r': 1, 'd': 1}
# [('l', 3), ('o', 2), ('H', 1), ('e', 1), (' ', 1), ('W', 1), ('r', 1), ('d', 1)]
itertools排列组合应用
5. 排列与组合
Python内置模块itertools集成了一系列与迭代器相关的函数,其中包括排列与组合功能。
5.1 排列
排列函数permutations接收一个可迭代对象,返回该对象内所有元素的全排列列表。
from itertools import permutations
print(list(permutations([1,2,3])))
# 输出如下:
# [(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
5.2 组合
组合函数combinations接收两个参数:第一个为待组合的可迭代对象,第二个为正整数,表示从列表中抽取的元素个数,返回所有可能的组合列表。
from itertools import combinations
print(list(combinations([1,2,3],2)))
# 输出如下:
# [(1, 2), (1, 3), (2, 3)]
函数传参与列表操作避坑指南
6. 实用编程技巧
6.1 Python区分可变类型与不可变类型,函数传参行为如下:
- 对于不可变类型(如字符串),传参时会进行深拷贝,在副本上修改不影响原数据;对于可变类型(如列表),传参时传递的是引用(浅拷贝),在函数内部对列表的操作会影响原列表。
- 若确实需要在函数内修改传入的可变类型而不影响外部,可在函数第一行进行深拷贝,例如:
def test(num_list:list):
# 进行深拷贝
num_list = num_list[:]
6.2 遍历列表时删除元素易导致索引错误
例如,列表为[1,2,3,4,5,6],若需删除所有偶数,直接通过索引查找并删除(如下错误示范),会导致后续元素前移,引发索引越界或漏删问题。
# 此处为错误示范!!!!!!!!
lst = [1, 2, 3, 4, 5, 6]
for i in range(len(lst)):
if lst[i] % 2 == 0:
lst.pop(i)
print(lst)
# 上面这段代码没有输出,因为报索引越界错误了。
正确的处理方式如下:
lst = [1, 2, 3, 4, 5, 6]
lst = [i for i in lst if i % 2 != 0]
print(lst)
# 输出如下:
# [1, 3, 5]
若需更复杂的筛选逻辑,可在if i%2 !=0处替换为判断函数,在函数内部实现具体的筛选逻辑。
6.3 安全访问字典元素
前文提及,普通dict字典无默认值,直接通过中括号键值查找value可能引发KeyError。
为避免此类错误,在通过key获取value时,建议使用字典的get()方法。
get()方法的第一个参数为key,第二个参数为可选值(默认为None)。当字典中不存在该key时,将返回第二个参数指定的默认值。
总结与展望
7. 总结
以上总结自笔者在Python算法刷题过程中的实践经验,若有不足之处,欢迎指正。
至此,关于Python算法常用技巧与内置库的详解介绍完毕。更多相关Python算法技巧与内置库内容,欢迎搜索本站往期文章或继续浏览后续文章。感谢大家的支持!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













