1. 字典序与数字序
最常见的两种排序依据是字典序和数字序。
字典序就是按照字符在ASCII码值表中的排列方式进行排序。
其较为严格的定义是,对于两个字符串x和y,
- 譬如"abandon"和"an"的比较,存在"abandon" < “an”,因为第二个字符"n"在字符串排在"b"后面
- 譬如"10"和"2"的比较,存在"10" < “2”,因为第一个字符"1"在中排在"2"前面。
- 空字符串比一切字符都小,譬如"abc"和"abcd"的比较,存在"abc" < “abcd”,因为"abc"的第四位可以看作是一个空字符串"“,该空字符串”“小于"d”。
可以简单理解为,字符串在现实中的字典里是怎么排布的,字典序就是怎么排布的。
数字序就是按照数字大小来进行排序。
- 譬如10和2的比较,显然10 > 2。
不管是字典序还是数字序,都有升序和降序两种排序形式,前者指从小到大排列,后者指从大到小排列。可以从下表的例子中看出他们之间的关系。

2. 稳定排序与不稳定排序
所谓稳定排序,是指key相同的元素在排序后的新数组中,其位置相对于原数组不发生改变。反之则成为不稳定排序。在常见的排序算法中,归并排序属于稳定排序,快速排序属于不稳定排序。
如题目:给定一个非空数组(列表),其中元素数据类型为整型,请按照数组元素十进制最低位从小到大进行排序,
十进制最低位相同的元素,相对位置保持不变,当数组元素为负值时,十进制最低位等同于去除符号位后对应十进制值最低位。
输入描述
给定一个非空数组,其元素数据类型为32位有符号整数,数组长度为[1,1000]。
输出描述
输出排序后的数组
示例
输入
1,2,5,-21,22,11,55,-101,42,8,7,32
输出
1,-21,11,-101,2,22,42,32,5,55,7,8
我们做出了如下的排序操作。
nums.sort(key = lambda x: int(x[-1]))
指定了字符串x的最后一个字符来作为排序依据key。当key相等时,元素会维持在原数组中的相对位置保持不变,达到稳定排序的结果。达到如下的输入、输出结果。
>>> 1,2,5,-21,22,11,55,-101,42,8,7,32 # 输入
>>> 1,-21,11,-101,2,22,42,32,5,55,7,8 # 输出
3. 三目运算符/三元表达式
三目运算符(有些人也称三元表达式)可以看作是if语句的简写版本,其基本语法结构为:
- 语句1 if 条件 else 语句2
其含义为:如果if后所带的条件满足,则会执行语句1,否则执行语句2。
ans = 1 if True else 0
print(ans) # 得到1
ans = 1 if False else 0
print(ans) # 得到0
print(1) if True else print(0) # 输出1
4. lambda匿名函数
匿名函数,顾名思义就是不需要具体定义函数名的函数。我们首先抛开复杂的定义,看两个具体例子。
先看一个无参数函数的例子。假设我们需要一个return 1的函数,如果使用普通的函数定义方式,其代码为:
# 使用def关键字进行函数定义
def get_one():
return 1
# 输出1
print(get_one())
如果我们使用lambda匿名函数,我们则不需要使用def关键字而改用lambda关键字,其代码为:
# 使用lambda匿名函数进行函数定义
# 注意这里的get_one_lambda看似是一个变量名
# 但实际上是一个函数名
get_one_lambda = lambda: 1
print(get_one_lambda)
# 输出形如 <function <lambda> at 0x00000249F7AD7B00> 的结果
# 这一长串复杂的十六进制数是函数get_one_lambda的内存地址
print(get_one_lambda())
# 在函数名get_one_lambda后面加上括号,才能正确地输出1
把上述代码进行合并操作,可以无需声明函数名get_one_lambda,直接得到结果:
# 这里的(lambda: 1)等价于上面的函数名get_one_lambda
# (lambda: 1)()等价于get_one_lambda()
# 显然get_one_lambda这个函数名是不必要的,故称之为"lambda匿名函数"
print(lambda: 1)
# 输出形如 <function <lambda> at 0x00000249F7AD7B00> 的结果
# 这一长串复杂的十六进制数是该匿名函数的内存地址
print((lambda: 1)())
# 在匿名函数后面加上括号,才能正确地输出1
再看一个有参数函数的例子。假设我们需要一个计算两个参数x和y相加return x+y的函数,如果使用普通的函数定义方式,其代码为:
# 使用def关键字进行函数定义
def add(x, y):
return x+y
# 输出30
print(add(10, 20))
如果我们使用lambda匿名函数,我们则不需要使用def关键字而改用lambda关键字,其代码为:
# 使用lambda匿名函数进行函数定义
# 注意这里的get_one_lambda看似是一个变量名
# 但实际上是一个函数名
add_lambda = lambda x,y: x+y
print(add_lambda)
# 输出形如 <function <lambda> at 0x00000249F7AD7B00> 的结果
# 这一长串复杂的十六进制数是函数add_lambda的内存地址
print(add_lambda(10, 20))
# 在函数名add_lambda后面加上括号并传入参数,才能正确地输出结果
把上述代码进行合并操作,可以无需声明函数名add_lambda,直接传入两个数字10和20,得到结果:
# 这里的(lambda x,y: x+y)等价于上面的函数名add_lambda
# (lambda x,y: x+y)(10, 20)等价于add_lambda(10, 20)
# 显然add_lambda这个函数名是不必要的,故称之为"lambda匿名函数"
print(lambda x,y: x+y)
# 输出形如 <function <lambda> at 0x00000249F7AD7B00> 的结果
# 这一长串复杂的十六进制数是该匿名函数的内存地址
print((lambda x,y: x+y)(10, 20))
# 在匿名函数后面加上括号并传入参数,才能正确地输出结果
根据上述两个例子已经能够看出端倪了,有以下重要结论:
- lambda匿名函数本质上是一个函数,而不是一个变量,使用lambda匿名函数可以得到一个函数。
- 定义lambda匿名函数的语法为:lambda [形参]: 返回值,形参的数量可以为0,即支持定义无参数匿名函数。
- 使用lambda匿名函数的语法为:(lambda [形参]: 返回值) ([实参]),一般情况下,实参的数量应该和定义的形参数量一致。
*注:上述语句中的中括号[]表示非必须结构。
最后我们来看lambda匿名函数在算法题中的几个经典用法。
1.defaultdict()
使用内置模块collections中的defaultdict(func),能够将哈希表的值value的默认类型设置为func,其中func是某种数据类型初始化函数的函数名,如int,list,dict等等。
假设我们想要value的默认值为1,其中一种方法通过def关键字定义一个叫做get_one()的函数,并且将函数名get_one作为函数名传入defaultdict(func)中。
from collections import defaultdict
# 使用def关键字进行函数定义
def get_one():
return 1
# 哈希表d的value的默认值即为1
# 注意这里传入的是函数名get_one,而不是调用函数get_one()
d = defaultdict(get_one)
# 输出1
print(d[0])
通过前面分析我们知道,get_one这个函数名实际上等同于lambda: 1,故我们不需要对get_one()显式地进行定义,使用lambda匿名函数能够使得代码更加整洁。
from collections import defaultdict
# 哈希表d1的value的默认值即为1
d_lambda = defaultdict(lambda: 1)
# 输出1
print(d_lambda[0])
2. sort()方法或内置函数sorted()
sort()方法和内置函数sorted()均包含key参数,用来指定排序的依据。key参数传入的也是一个函数名func,可以简单理解为对列表中的所有元素均使用函数func后,以得到的结果为依据对原列表进行排序。
假设我们想要对字符串按照长度来排序,可以指定len()内置函数为排序依据。
lst = ["123", "4567", "0", "12", "789"]
# 注意这里key参数传入是函数名len,而不是调用函数len()
lst.sort(key = len)
# 输出['0', '12', '123', '789', '4567']
print(lst)
上述代码也可以写成lambda匿名函数的形式。
lst = ["123", "4567", "0", "12", "789"]
# 注意这里key参数传入是函数名len,而不是调用函数len()
lst.sort(key = lambda x: len(x))
# 输出['0', '12', '123', '789', '4567']
print(lst)
如果对于长度相同的字符串,我们还想要按照数字序降序排列,那么仅用len()函数是无法完成的,只能通过进一步完善lambda匿名函数来完成。
lst = ["123", "4567", "0", "12", "789"]
# 注意这里key参数传入是函数名len,而不是调用函数len()
lst.sort(key = lambda x: (len(x), -int(x)))
# 输出['0', '12', '789', '123', '4567']
print(lst)
3. 内置函数map()
内置函数map(func, iter)包含两个参数,分别是函数名func和可迭代对象iter。这里的func参数不仅可以传入已有的内置函数或自定义函数的函数名,也可以直接用lambda匿名函数完成。
譬如想要对由二进制字符串组成的列表lst_bin中的每一个字符串用先导0填充至长度为4,如果我们使用def关键字定义一个叫做get_pre_0()的函数,可以这样实现:
# 使用def关键字进行函数定义
# (4-len(s))能够获得应该填充的先导零"0"的个数
def get_pre_0(s):
return (4-len(s))*"0" + s
lst_bin = ["10", "101", "1100", "1", "0"]
# 注意这里key参数传入是函数名get_pre_0,而不是调用函数get_pre_0()
lst_with_pre_0 = list(map(get_pre_0, lst_bin))
# 输出['0010', '0101', '1100', '0001', '0000']
print(lst_with_pre_0)
使用lambda匿名函数,无需具体定义函数get_pre_0(),亦可完成同样操作。
lst_bin = ["10", "101", "1100", "1", "0"]
# 使用lambda匿名函数,无需具体定义函数get_pre_0()
lst_with_pre_0_lambda = list(map(lambda x: (4-len(x))*"0" + x, lst_bin))
# 输出['0010', '0101', '1100', '0001', '0000']
print(lst_with_pre_0_lambda)
5. 基于元素之间关系的排序
在对数据进行排序的时候,可能会遇到这样一种情况。
我们希望通过比较元素之间的关系来进行排序,而非基于元素自身相关的某个指标来进行排序。
那么单纯使用参数key和lambda匿名函数就无法满足我们的需求了。
譬如【贪心】2024E-卡片组成的最大数字(小组中每个人都有一张卡片,卡片上是6位以内的正整数,组员将卡片连起来可以组成多种数字。计算可以组成的最大数字。)中遇到的情形,我们希望字符串数组中,两两元素之间,按照能拼接出更大字典序的方式来排列。
在Python中我们可以通过导入functools中的cmp_to_key函数并结合传入函数名compare实现这样的排序。
其具体代码如下
from functools import cmp_to_key
# 用来作为cmp_to_key的比较函数
def compare(x, y):
# x+y组成的数字更大,x应该排在y前面
if x+y > y+x:
return -1
# y+x组成的数字更大,y应该排在x前面
elif x+y < y+x:
return 1
# x+y和y+x组成的数字一样大,x和y之间的位置关系无关紧要
else:
return 0
# 对输入根据","进行切割,得到字符串数组lst
lst = input().split(",")
# 对lst根据compare的比较规则,调用cmp_to_key函数进行排序
lst.sort(key = cmp_to_key(compare))
# 对lst排序后的结果进行输出
print("".join(lst))
或者
from functools import cmp_to_key
# 用来作为cmp_to_key的比较函数
def compare(x, y):
# x+y组成的数字更大,x应该排在y前面
if x+y > y+x:
return -1
# y+x组成的数字更大,y应该排在x前面
elif x+y < y+x:
return 1
# x+y和y+x组成的数字一样大,x和y之间的位置关系无关紧要
else:
return 0
# 对输入根据","进行切割,得到字符串数组lst
lst = input().split(",")
# 对lst根据构建的lambda匿名函数为比较规则,调用cmp_to_key函数进行排序
lst.sort(key = cmp_to_key(lambda x, y: int(y+x)-int(x+y)))
# 对lst排序后的结果进行输出
print("".join(lst))
6. ord()与chr()
这两个函数用于ASCII码值和对应字符之间的转化。
- ord(ch)可以将字符ch转化为对应ASCII码值。其中ord()表示的是order的意思。
- chr(num)可以将ASCII码值num转化为对应字符。其中chr()表示的是character的意思。
print(ord("0")) # 得到48
print(ord("A")) # 得到65
print(ord("B")) # 得到66
print(ord("a")) # 得到97
print(ord("b")) # 得到98
print(chr(48)) # 得到"0"
print(chr(66)) # 得到"C"
print(chr(67)) # 得到"D"
print(chr(99)) # 得到"c"
print(chr(100)) # 得到"d"
一个常用的技巧是,如果有一个小写字母ch,想令其拥有形如"a": 0, “b”: 1,“c”: 2,…,“z”: 25这样的字母表顺序映射关系,我们可以通过以下代码得到。
# 假设字符ch是"c",其映射在字母表中顺序的映射应该是2
ch = "c"
# ord(ch)是99,ord("a")是97,相减得到2
idx = ord(ch) - ord("a")
这个技巧常用于用列表表示已知范围的哈希表的操作。
关于ASCII码值更加详细的内容,可以看百度百科-ASCII码值。
7. all()与any()
内置函数all()与any()中传入的是一个可迭代对象iter(如一个列表lst),返回值为一个布尔类型。
- 对于all(iter)而言,如果可迭代对象iter中所有的值均为True,那么all(iter)会返回True,否则返回False
- 对于any(iter)而言,如果可迭代对象iter中任意的值满足True,那么any(iter)会返回True,否则返回False
print(all([True, True, True]))
print(all([True, True, False]))
print(any([False, False, False]))
print(any([True, True, False]))
注意这两个内置函数,经常搭配推导式一起使用。
8. 全局变量
如果在自定义函数体外声明了一个全局变量,可以用在函数体中用global关键字来修饰这个全局变量,那么该变量就可以在函数体中直接使用了。这个技巧在DFS题目中非常实用,用于避免写出逻辑复杂的return返回值。
def f():
global ans
ans += 1
ans = 0
print(ans)
f()
print(ans)
9. 最大递归深度
当我们在做DFS和回溯题目的时候,通常会使用递归来实现。Python中默认的最大递归深度是1000,但有时候由于用例本身的问题,程度的递归深度会超过1000,就会出现如下的报错
RecursionError: maximum recursion depth exceeded in comparison
在确认代码无误的情况下,我们可以使用sys内置库中的setrecursionlimit()方法,来重新设置最大递归深度,其具体代码如下
import sys
sys.setrecursionlimit(1000000) # 这里不一定设置1000000,设置成一个很大的数即可
上述两行代码加在程序的最前方即可。
1万+



被折叠的 条评论
为什么被折叠?



