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

posted @ 2024-04-04 02:36  purinliang  阅读(13)  评论(0编辑  收藏  举报