XCPCer速通Python
众所周知C++是几乎所有XCPCer的主语言,但是有一些时候(比如不想写大数乘法、在LeetCode上做题、参加笔试面试)不得不使用在某些场景下更好写的Python。Python的标准库提供了很多很实用的功能,而且有着非常人性化的异常(比如数组越界、除以零等),最重要的是它内置的print函数可以非常方便的输出各种各样复杂的嵌套结构(C++需要自己写一个函数进行输出,或者每次都使用for循环等,在LeetCode/笔试面试阶段非常不利)。
这篇文章记录了一些我觉得Python提供了内置的方便的库函数,并且有一些坑是可能需要注意的也会记录下来。
输入
一般是用字符串的split(),无参的split会自动过滤各种空白字符,不要画蛇添足。(注意split(" ")会把"1 2"分割为["1", "", "2"])其返回一个列表,使用列表推导式把他分别赋值给需要的数据类型就可以了。
a, b, c = [int(i) for i in input().split()] # 一行输入多个整数
print(a, b, c) # 1 2 3
print(a + b + c) # 6
a, b, c = input().split() # 一行输入多个字符串
print(a, b, c) # 1 2 3 注意这里是字符串
print(a + b + c) # 123
多组输入的模板:
def solve():
a, b, c = [int(i) for i in input().split()]
while b % 3 and c:
b += 1
c -= 1
if b % 3:
print(-1)
return
res = a + b // 3 + (c + 2) // 3
print(res)
return
# t = 1
t = int(input())
while t:
t -= 1
solve()
输入可以在PyCharm里面选择运行的虚拟环境,然后配置,输入从文件中重定向。
出现混合输入的情况就分别进行转换即可。
二进制输入
a_str = "1001"
a_int = int(a_str, 2)
print(a_int) # 9
b_int = 12
b_str = bin(b_int)
print(b_str) # 0b1100
print(b_str[2:]) # 1100
逻辑表达式
python中也有三元表达式:
max_a_b = a if a >= b else b
python中的各种广义的空元素,比如空的列表,空的字符串,值为0的数字,为None的对象,都能代表逻辑False
stack = [1, 2, 3, 4, 5]
while stack:
print(stack)
stack.pop()
# [1, 2, 3, 4, 5]
# [1, 2, 3, 4]
# [1, 2, 3]
# [1, 2]
# [1]
print(stack) # []
print("True" if stack else "False") # False
stack.append(0)
print(stack) # [0]
print("True" if stack else "False") # True
value = 0
print("True" if value else "False") # False
value = 1
print("True" if value else "False") # True
TreeNode = None
print("True" if TreeNode else "False") # False
word = ""
print("True" if word else "False") # False
word = "abcd"
print("True" if word else "False") # True
value = 0.0
print("True" if value else "False") # False
value = 1.0
print("True" if value else "False") # True
需要注意的是,包括一个空列表的列表并不是空列表。
my_list = []
print("True" if my_list else "False") # False
my_list = [[]]
print("True" if my_list else "False") # True
数学
Python中的一些简单的数学行为和C++的不一样,要特别注意
INF_float = 1e9
print(INF_float) # 1000000000.0
INF_int = 10**9
print(INF_int) # 1000000000
print(5 / 2) # 2.5
print(5 // 2) # 2
print(7 % 3) # 1
print(-7 % 3) # 2 注意这个和C++的显著区别,Python的%是会自动变回非负数的
print(5 // 0) # ZeroDivisionError: integer division or modulo by zero
Python中还有更好用的函数,可以对列表或者列表切片等使用。
my_list = [i for i in range (1, 6)]
print(my_list) # [1, 2, 3, 4, 5]
print(sum(my_list)) # 15
print(min(my_list)) # 1
print(max(my_list)) # 5
my_list = [1, 1, 2, 2, 3, 3]
print(my_list.index(min(my_list))) # 0 返回第一个最小值的位置
print(my_list.index(max(my_list))) # 4 返回第一个最大值的位置
列表 List
https://docs.python.org/zh-cn/3/tutorial/datastructures.html
常用方法
见上面的链接,说得非常清楚,基本上都和C++的vector的方法一模一样。与众不同的是:
len(my_list) # vector.size()
my_list.append(x) # vector.push_back(x);
x = my_list.pop() # x = vector.back(); vector.pop_back();
my_list.sort() # sort(vector.begin(), vector.end()) 原地排序
my_list.sort(reversed=True) # sort(vector.begin(), vector.end(), greater<int>())
my_list.reverse() # reverse(vector.begin(), vector.end())
set(my_list) # 在不修改list的前提下返回一个无重的set,不一定保证有序 https://docs.python.org/zh-cn/3/tutorial/datastructures.html#sets
sorted(my_list) # 在不修改原本的list的情况下返回一个新的有序list
my_list= list(set(sorted(my_list))) # 排序并去重
二维列表
生成一个初始值为0的,长度为n的空列表:
n = 10
my_list_1 = [0] * n # 如果要生成包含n个空列表的列表,则是[[]] * n
my_list_2 = [0 for _ in range(n)] # 注意这个列表推导式要用[]括起来才是列表
print(my_list_1, my_list_2) # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
my_list_1[1] = 1
my_list_2[2] = 2
print(my_list_1, my_list_2) # [0, 1, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 2, 0, 0, 0, 0, 0, 0, 0]
print(my_list_1[n]) # IndexError: list index out of range
看起来没啥问题是吧?看看二维列表
n = 3
my_list_1 = [[0] * n] * n
my_list_2 = [[0 for _ in range(n)] for _ in range(n)]
print(my_list_1, my_list_2) # [[0, 0, 0], [0, 0, 0], [0, 0, 0]] [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
my_list_1[1][1] = 1
my_list_2[2][2] = 2
print(my_list_1, my_list_2) # [[0, 1, 0], [0, 1, 0], [0, 1, 0]] [[0, 0, 0], [0, 0, 0], [0, 0, 2]]
用第一种方法生成的列表,其的引用被复制了n次。而对于内置的整数类型0来说,没有这个问题。意味着如果生成的是引用类型,要注意区分,一般都是应该用第二种方法生成的。
当然由于0没问题,所以下面的写法也是可以的
n = 3
my_list_3 = [[0] * n for _ in range(n)]
my_list_4 = [0 for _ in range(n)] * n # 这种方法生成的是一维列表
print(my_list_3, my_list_4) # [[0, 0, 0], [0, 0, 0], [0, 0, 0]] [0, 0, 0, 0, 0, 0, 0, 0, 0]
my_list_3[1][1] = 1
my_list_4[2] = 2
print(my_list_3, my_list_4) # [[0, 0, 0], [0, 1, 0], [0, 0, 0]] [0, 0, 2, 0, 0, 0, 0, 0, 0]
当然,为了防止迷惑,我觉得最好统一养成用my_list_2的写法的习惯。
逆序下标访问列表
python可以通过负数下标枚举列表:
n = 6
my_list = [i for i in range(n)]
print(my_list) # [0, 1, 2, 3, 4, 5]
# 用负数逆序枚举列表
print(my_list[-1], my_list[-2]) # 5 4
# 负0和正0都是0,没有区别
print(my_list[-0], my_list[+0]) # 0 0
# 要记得逆序的时候,最后一个元素是-n而不是-(n - 1)
print(my_list[-n], my_list[-(n - 1)]) # 0 1
逆序枚举列表
使用range逆序枚举的时候,要写清楚3个参数。只写两个参数的时候,结果如下:
n = 6
my_list = [i for i in range(n)]
print(my_list) # [0, 1, 2, 3, 4, 5]
print([my_list[i] for i in range(n - 1, -1)]) # [] 输出是空列表
print([i for i in range(n - 1, -1)]) # [] 因为range(n - 1, -1) 是空列表
print([my_list[i] for i in range(-1, -n - 1)]) # [] 输出是空列表
print([i for i in range(-1, -n - 1)]) # [] 因为range(-1, -n - 1) 是空列表
写清楚三个参数之后就可以了,我相信在XCPC里面第二种、第三种写法是比较常用的。
n = 6
my_list = [i for i in range(n)]
print(my_list) # [0, 1, 2, 3, 4, 5]
print([my_list[i] for i in range(n - 1, -1, -1)]) # [5, 4, 3, 2, 1, 0]
print([my_list[i] for i in range(-1, -n - 1, -1)]) # [5, 4, 3, 2, 1, 0]
print([my_list[i] for i in reversed(range(n))]) # [5, 4, 3, 2, 1, 0] 推荐,生成了逆序的下标i,方便处理
如果不需要用到下标i,则直接生成一个反向的列表就可以了:
n = 6
my_list = [i for i in range(n)]
print(my_list) # [0, 1, 2, 3, 4, 5]
print(my_list[::-1]) # [5, 4, 3, 2, 1, 0] 要么只写最后一个步长-1
print(my_list[-1:-n - 1:-1]) # [5, 4, 3, 2, 1, 0] 要么必须写够3个参数
print(reversed(my_list)) # <list_reverseiterator object at 0x7fc4e27655b0>
print([reversed(my_list)]) # [<list_reverseiterator object at 0x7fc4e27655b0>]
my_list.reverse() # 此方法会修改my_list
print(my_list) # [0, 1, 2, 3, 4, 5]
交换列表的元素或者部分翻转列表:
可以从nextPermutation这道题获得启发,
交换列表的元素直接用python的多重赋值来解决。
部分翻转列表可以使用python的字符串切片的方法,用一点点额外的内存简化写法。
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
cur = n - 1
while cur - 1 >= 0 and nums[cur - 1] >= nums[cur]:
cur -= 1
if cur == 0:
nums.reverse()
return
pos = cur - 1
cur = n - 1
while nums[cur] <= nums[pos]:
cur -= 1
nums[cur], nums[pos] = nums[pos], nums[cur]
nums[pos + 1 :n] = nums[pos + 1: n][::-1]
return nums
字符串 str
字符串的长度,注意写法和C++的不太一样
my_str = "Hello world!"
print(my_str) # Hello world!
print(len(my_str)) # 12
# print(my_str.len) # 崩溃
# print(my_str.length) # 崩溃
# print(my_str.size) # 崩溃
字符串是不可变的对象,如果要想模仿C++的string的话,可以用字符(串)列表。然后在输出的时候从这个列表生成一个新的字符串。
mutable_str = [i for i in "abcdefg"]
print(mutable_str) # ['a', 'b', 'c', 'd', 'e', 'f', 'g']
print("".join(mutable_str)) # abcdefg
mutable_str.append("h")
mutable_str[0] = "A"
print(mutable_str) # ['A', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
print("".join(mutable_str)) # Abcdefgh
由于此方法是用list代替的str,所以可以使用list的sort或者reverse对字符串进行调整,或者count或者index之类的方法去寻找单个字符。
字符串也没有reverse方法,要使用一些奇怪的办法去实现,一般应该是生成一个反向的字符串切片覆盖到原本的上面
my_str = "Hello world!"
# my_str.reverse() # 崩溃
# reverse(my_str) # 崩溃
print(reversed(my_str)) # <reversed object at 0x7fcea5f158b0>
print("".join(reversed(my_str))) # !dlrow olleH
print(my_str[::-1]) # !dlrow olleH
字符串有find方法,找到第一个匹配的下标的位置,找不到返回-1
my_str = "abcabcabc"
print(my_str.find("abc")) # 0
print(my_str.find("cab")) # 2
print(my_str.find("d")) # -1
字符串有count方法,找到所有不重叠的子串出现的次数。
my_str = "abababab"
print(my_str.count("ab")) # 4
print(my_str.count("abab")) # 2
二分查找
https://docs.python.org/zh-cn/3.12/library/bisect.html
# bisect.bisect_left(a, x, lo=0, hi=len(a), *, key=None) # 第一个大于等于x的元素,相当于C++的lower_bound
# bisect.bisect_right(a, x, lo=0, hi=len(a), *, key=None) # 第一个大于x的元素,相当于C++的upper_bound
# bisect.insort_left(a, x, lo=0, hi=len(a), *, key=None) # 插入到第一个大于等于x的元素的左边
# bisect.insort_right(a, x, lo=0, hi=len(a), *, key=None) # 插入到第一个大于x的元素的左边(最后一个小于等于x的元素的右边)
import bisect
my_list = [1, 2, 3, 4, 4, 4, 4, 5, 6]
print(bisect.bisect_left(my_list, 4)) # 3 第一个4的位置
print(bisect.bisect_right(my_list, 4)) # 7 最后一个4的下一个位置,也就是5
bisect.insort_left(my_list, 4)
print(my_list) # [1, 2, 3, 4, 4, 4, 4, 4, 5, 6]
bisect.insort_right(my_list, 7)
print(my_list) # [1, 2, 3, 4, 4, 4, 4, 4, 5, 6, 7]
例题:
def searchRange(self, nums: List[int], target: int) -> List[int]:
left = bisect.bisect_left(nums, target)
if left == len(nums) or nums[left] != target:
return [-1, -1]
right = bisect.bisect_right(nums, target) - 1
return [left, right]
更多复杂的判断可以点进链接自己看。
奇怪的列表枚举 itertools
https://docs.python.org/3/library/itertools.html
import itertools
n = 5
my_list = [i for i in range(1, 1 + n)]
print(my_list) # [1, 2, 3, 4, 5]
accumulated_my_list = [i for i in itertools.accumulate(my_list)]
print(accumulated_my_list) # [1, 3, 6, 10, 15]
对同一个列表还有一些看起来有用的方法:
product: 相当于n维for循环的笛卡尔积,可能在Floyd算法可以用?
permutations: 排列数(按位置区分元素而不是按值区分),可以代替C++的next_permutation使用
combinations: 组合数
import itertools
two_dimension_product = [[i, j] for i, j in itertools.product(range(3), repeat=2)]
print(two_dimension_product) # [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]]
A_3_3 = [l for l in itertools.permutations([1, 2, 3])] # 位置的排列数,(位置的)字典序
print(A_3_3) # [(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
A_3_2 = [l for l in itertools.permutations([1, 2, 3], 2)] # 位置的排列数,(位置的)字典序
print(A_3_2) # [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
C_3_3 = [l for l in itertools.combinations([1, 2, 3], 3)] # 位置的组合数,(位置的)字典序
print(C_3_3) # [(1, 2, 3)]
C_3_2 = [l for l in itertools.combinations([1, 2, 3], 2)] # 位置的组合数,(位置的)字典序
print(C_3_2) # [(1, 2), (1, 3), (2, 3)]
C_3_3_special = [l for l in itertools.combinations_with_replacement([1, 2, 3], 3)] # 同一个位置可以重复选的组合数
print(C_3_3_special) # [(1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 2), (1, 2, 3), (1, 3, 3), (2, 2, 2), (2, 2, 3), (2, 3, 3), (3, 3, 3)]
C_3_2_special = [l for l in itertools.combinations_with_replacement([1, 2, 3], 2)] # 同一个位置可以重复选的组合数
print(C_3_2_special) # [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]
# 这些方法说的重复是指位置重复,与值是否重复是无关的
A_3_2_replacement = [l for l in itertools.permutations([1, 1, 2], 2)]
print(A_3_2_replacement) # [(1, 1), (1, 2), (1, 1), (1, 2), (2, 1), (2, 1)]
C_3_2_replacement = [l for l in itertools.combinations([1, 1, 2], 2)]
print(C_3_2_replacement) # [(1, 1), (1, 2), (1, 2)]
简单数据结构
栈
栈用list来模仿刚刚好。基本上是完全一致的。
队列
如果使用内置的双端队列,和C++的queue是一样的体验。https://docs.python.org/zh-cn/3/library/collections.html#deque-objects
其实列表可以一边遍历一边append,所以也可以这样写BFS:
Q = []
vis[1] = True
Q.append(1)
for u in Q:
for v in G[u]:
if not vis[v]:
vis[v] = True
Q.append(v)
在实现BFS时,C++也可以用vector来代替queue,其实更好写。写法和上面相同。
集合
set对应的大概是C++的unordered_set。
详见:https://docs.python.org/zh-cn/3/library/stdtypes.html#set-types-set-frozenset
映射 / 字典
python的字典对应的大概是C++的unordered_map,不过字典的顺序是插入顺序。
详见:https://docs.python.org/zh-cn/3/library/stdtypes.html#mapping-types-dict