2024/12/11 【字符串】LeetCode344.反转字符串 【√】解法众多:双指针,栈,range,reversed,切片,列表推导,reverse,交换函数实现的两种方法
解法1:双指针
因为while每次循环需要进行条件判断,而range函数不需要,直接生成数字,因此时间复杂度更低。推荐使用range
class Solution: def reverseString(self, s: List[str]) -> None: """ Do not return anything, modify s in-place instead. """ n = len(s) i, j = 0, n-1 while i < j: #1.常见的交换数值 # temp = s[i] # s[i] = s[j] # s[j] = temp #2.通过位运算的异或运算实现交换两个数的操作 # s[i] = chr(ord(s[i]) ^ ord(s[j])) # s[j] = chr(ord(s[i]) ^ ord(s[j])) # s[i] = chr(ord(s[i]) ^ ord(s[j])) #3. s[i], s[j] = s[j], s[i] i += 1 j -= 1
1.C++中库函数swap()(交换函数)在Python中的对应
在 Python 中,C++ 的 swap()
函数的功能(交换两个变量的值)可以直接通过 Python 的多重赋值实现,而不需要一个显式的库函数。
原理
Python 中的 a, b = b, a
是一个简洁的语法糖,实际上等价于:
- 创建一个元组
(b, a)
,然后将其解包赋值给a
和b
。 - 这一过程不会引入额外的临时变量。
这比 C++ 的 swap()
函数更直观且更简洁。
如果需要类似 swap()
的函数
虽然 Python 本身没有内置的 swap()
函数,但我们可以自己定义一个:
def swap(a, b): return b, a # 使用 swap 函数 x = 5 y = 10 x, y = swap(x, y) print(x) # 输出:10 print(y) # 输出:5
这种方式和多重赋值本质上是一样的,只是通过函数形式进行了封装。
与 C++ 的 swap()
对比
特性 | C++ 的 swap() | Python 的多重赋值 |
---|---|---|
语法 | std::swap(a, b); |
a, b = b, a |
是否需要库支持 | 需要(<utility> ) |
不需要,原生支持 |
操作对象 | 基本类型或对象 | 任意可赋值的对象(变量、列表元素等) |
是否高效 | 高效(通常由编译器优化为原地交换) | 高效(无需额外内存分配) |
2.swap函数实现的两种方式
swap可以有两种实现。
一种就是常见的交换数值:如1
一种就是通过位运算:如2
位运算是一种直接对二进制位进行操作的运算,通常用于底层编程、硬件控制和性能优化等场景。
位运算操作的对象是整数类型的数据(比如 int
),这些数据在计算机中以二进制形式存储。
位运算的主要特点是高效,因为它直接在二进制层面进行计算,通常比其他运算(如加减乘除)快。
按位异或(^)(常见位运算操作之一,还包括按位与(&),按位或(|),按位取反(~),左移(<<),右移(>>))
- 功能:两个数的每个位都进行逻辑异或操作(相同为 0,不同为 1)。
- 作用:可以用来翻转特定的二进制位。
- 示例:
位运算的应用场景
-
快速计算:
- 通过移位实现乘法和除法(如
x << 1
等价于x * 2
)。
- 通过移位实现乘法和除法(如
-
标志位操作:
- 用于设置、清除、切换特定位。例如,用按位与清除某个位,用按位或设置某个位。
-
判断奇偶性:
n & 1 == 0
表示偶数,n & 1 == 1
表示奇数。😍- 与操作(AND)是一种基本的二进制位运算,在计算机科学中非常常用。它用于比较两个二进制数的每个位,只在两个位同时为1时结果为1,否则为0。
-
位掩码:
- 通过掩码操作(与运算)保留特定的二进制位。
-
交换两个数:
- 通过异或运算实现:
-
a = a ^ b b = a ^ b a = a ^ b
3. 怎么理解通过异或运算实现交换两个数?
通过异或运算实现交换两个数的原理,利用了异或运算的性质:
- a⊕a=0 (一个数异或自身等于 0)
- a⊕0=a(一个数异或 0 等于自身)
- a⊕b⊕b=a(因为 b⊕b=0,所以 a⊕b⊕b=a)
实现步骤
设两个数分别为 a和 b,通过以下三步实现交换:
-
第一步:
a=a⊕b
此时,a 保存的是 a 和 b 的异或值。 -
第二步:
b=a⊕b
因为 a 现在是 a⊕b,所以 b 变成了:
b=(a⊕b)⊕b=a⊕(b⊕b)=a⊕0=a
此时 b 保存了原来的 a 值。 -
第三步:
a=a⊕b
因为 b 现在是原来的 a,所以 a 变成了:
a=(a⊕b)⊕b=a⊕(b⊕b)=a⊕0=b
此时 a 保存了原来的 b 值。
代码示例
以下是用 Python 实现的代码:
# 初始值 a = 5 # 二进制:0101 b = 3 # 二进制:0011 # 交换过程 a = a ^ b # a = 0101 ^ 0011 = 0110 (a 变为 6) b = a ^ b # b = 0110 ^ 0011 = 0101 (b 变为 5) a = a ^ b # a = 0110 ^ 0101 = 0011 (a 变为 3) print("a =", a) # 输出:a = 3 print("b =", b) # 输出:b = 5
用异或实现交互两个数的操作为什么可以不用临时变量?
普通交换两个数的代码需要一个临时变量,通过异或实现交换的方式,不需要额外的变量,因为所有的信息都保存在 a 和 b 的二进制中。它通过数学逻辑直接完成交换。
注意事项
-
如果 a 和 b 指向同一个内存位置(如 a 和 b 是同一个变量),则这种方法不适用。因为会导致数据被清零:
a=a⊕a=0 -
在实际开发中,虽然异或交换节省了一个临时变量,但现代编译器优化已经非常强大,普通交换(用临时变量)也能生成高效的代码。因此异或交换主要用作教学示例或在极端优化场景下使用。
4. 如果是两个字符进行交换可以用这个方法吗?
异或运算可以用于交换两个字符,因为字符在底层是以整数形式(ASCII 或 Unicode 值)存储的,而异或操作本质上是对整数进行操作。只要将字符视为整数,就可以使用同样的方法实现交换。
Python 示例
以下是交换两个字符的代码:
# 初始值 a = 'A' # 字符 'A' 的 ASCII 值是 65 b = 'B' # 字符 'B' 的 ASCII 值是 66 # 交换过程 a = chr(ord(a) ^ ord(b)) # 将字符转换为整数进行异或,再转回字符 b = chr(ord(a) ^ ord(b)) a = chr(ord(a) ^ ord(b)) # 输出结果 print("a =", a) # 输出:a = B print("b =", b) # 输出:b = A
解释
ord()
:将字符转换为其对应的 ASCII 或 Unicode 值。例如,ord('A')
返回65
。chr()
:将整数值转换回字符。例如,chr(65)
返回'A'
。
通过 ord()
和 chr()
,可以在字符和整数之间自由转换,从而使用异或交换字符。
注意事项
- 适用范围:字符的交换适用于所有单个字符(如
'a'
、'A'
、'你'
),因为它们都可以被映射为整数。 - 不要用于字符串:如果需要交换的是多字符字符串,这种方法就不适用了,因为字符串不直接对应单个整数。
5.C++有^=这个符号,Python里面有^=这个符号吗?
是的,Python 中有 ^=
运算符,它是一种按位异或赋值运算符。
注意事项
^=
的作用范围:只适用于整数和可以转换为整数的数据(如字符)。- 与其他运算符类似:
^=
的用法与+=
、-=
、*=
等运算符一致,只是它对应的是异或操作。
解法2:使用栈
class Solution: def reverseString(self, s: List[str]) -> None: """ Do not return anything, modify s in-place instead. """ stack = [] for char in s: stack.append(char) for i in range(len(s)): s[i] = stack.pop()
1. Python中栈的表达:
stack = []
2. Python中加入元素到栈,和从栈中弹出元素的方法:
#加入栈 stack.append(char) #弹出栈 s[i] = stack.pop()
解法3:使用range
class Solution: def reverseString(self, s: List[str]) -> None: n = len(s) for i in range(n//2): s[i], s[n-i-1] = s[n-i-1], s[i] i += 1
1. python中“//”和“/”的区别是什么?
在 Python 中,/
和 //
都是用于除法运算的运算符,但它们的行为有所不同:
1. /
运算符
- 功能:执行浮点除法(返回浮点数)。
- 结果:即使两个操作数是整数,结果也是一个浮点数。
示例:
print(5 / 2) # 输出:2.5 print(4 / 2) # 输出:2.0 print(7 / 3) # 输出:2.3333333333333335
- 适用场景:当需要精确的除法结果(包括小数部分)时使用
/
。
2. //
运算符
- 功能:执行整数除法(也称为地板除,floor division)。
- 结果:返回商的整数部分,向下取整(舍去小数部分)。
- 如果操作数中有浮点数,结果仍为浮点数,但小数部分为
.0
。 - 注意:
//
的向下取整会处理负数,例如 −3.5-3.5 向下取整为 −4-4。
- 如果操作数中有浮点数,结果仍为浮点数,但小数部分为
示例:
print(5 // 2) # 输出:2 print(4 // 2) # 输出:2 print(7 // 3) # 输出:2 print(-7 // 3) # 输出:-3 print(7.0 // 3) # 输出:2.0
- 适用场景:当只需要商的整数部分时使用
//
,通常用于循环计算或整数分组场景。
扩展:和 %
的关系
%
:取模运算符,返回除法的余数。//
和%
的关系:可以表示为: a=(a//b)×b+(a%b)
例如:
a = 7 b = 3 print(a // b) # 输出:2 print(a % b) # 输出:1 # 验证关系 print((a // b) * b + (a % b)) # 输出:7
解法4:使用reversed
class Solution: def reverseString(self, s: List[str]) -> None: s[:] = reversed(s)
1. 介绍reversed函数和reverse函数
在 Python 中,reversed()
和 .reverse()
是两种不同的功能,用于反转序列的顺序。它们的用法和作用范围有所不同。
1. reversed()
函数
- 类型:内置函数。
- 作用:返回一个反转后的迭代器,不改变原序列。
- 适用对象:任何可迭代对象(如字符串、列表、元组、range 等)
特点:
- 返回值:返回一个迭代器对象。
- 不会修改原序列,而是提供一个新的迭代器。
- 可以通过
list()
、tuple()
或str().join()
将结果转为具体的数据结构。
示例:
# 示例 1:反转列表 lst = [1, 2, 3, 4] rev_lst = reversed(lst) print(list(rev_lst)) # 输出:[4, 3, 2, 1] print(lst) # 原列表未改变:[1, 2, 3, 4] # 示例 2:反转字符串 s = "hello" rev_s = reversed(s) print("".join(rev_s)) # 输出:'olleh' # 示例 3:反转元组 tup = (10, 20, 30) print(tuple(reversed(tup))) # 输出:(30, 20, 10)
2. .reverse()
方法
- 类型:列表的方法。
- 作用:原地反转列表的顺序。
- 适用对象:仅适用于列表,不能用于其他序列(如字符串、元组)。
用法:
list.reverse()
特点:
- 返回值:无返回值(
None
),直接修改原列表。 - 只能用于列表,不能用于字符串或元组等不可变序列。
总结与选择
reversed()
:更通用,适用于需要临时反转任意可迭代对象,并且不想修改原对象的场景。.reverse()
:更高效,适用于需要直接修改列表顺序的场景。
2.为什么表达成
s[:] = reversed(s)可以正确运行,但是
s = reversed(s)不行,以及
s[:]是什么意思?
在 Python 中,s[:] = reversed(s)
和 s = reversed(s)
的行为不同,是因为它们涉及到切片赋值和变量重新绑定的区别。让我们逐一分析。
1. s[:] = reversed(s)
为什么可以运行?
这里的 s[:]
是对列表的切片操作,表示“对列表 s
的所有元素进行赋值”。
这实际上是修改列表的内容而不是重新绑定变量 s
,因此它可以正确运行。
原理:
s[:]
是一个切片表达式,指代列表s
的全部内容。reversed(s)
返回一个迭代器,表示反转后的元素顺序。s[:] = reversed(s)
的意思是“将s
的所有元素替换为reversed(s)
中的值”。
为什么适用?
- 切片赋值允许你直接修改列表内容,而不会更改原列表的引用(地址)。
- 列表的内容被反转后的值替换,而原变量
s
仍然指向同一个列表对象。
2. s = reversed(s)
为什么不行?
在 s = reversed(s)
中:
s
被重新绑定到reversed(s)
返回的迭代器对象。- 原列表
s
不再存在,而是被变量s
覆盖。
结果:
- 变量
s
不再是一个列表,而是一个迭代器。 - 如果你试图对迭代器进行像列表一样的操作(如索引、切片等),就会报错。
例如:
s = [1, 2, 3, 4] s = reversed(s) # 现在 s 是一个迭代器 print(list(s)) # 输出:[4, 3, 2, 1](可以通过 list() 再转回来) # print(s[0]) # 报错:'reversed' object is not subscriptable
总结:
s[:] = reversed(s)
修改了列表的内容。s = reversed(s)
将变量s
绑定为一个新的迭代器,丢失了列表的性质。
3. s[:]
的含义
[:]
是一个切片表达式,它有以下特点:
- 指代整个序列的所有元素。
- 对于列表来说,
s[:]
返回一个与s
内容相同的新列表(浅拷贝)。 - 当用于赋值时(如
s[:] = ...
),它不会创建新的列表对象,而是直接修改s
的内容。
示例:
# 浅拷贝 s = [1, 2, 3] copy_s = s[:] # 创建了一个与 s 内容相同的新列表 print(copy_s) # 输出:[1, 2, 3] print(copy_s is s) # 输出:False(不是同一个对象) # 切片赋值 s = [1, 2, 3] s[:] = [4, 5, 6] # 修改 s 的所有元素 print(s) # 输出:[4, 5, 6]
总结
s[:] = reversed(s)
修改了列表s
的内容,保持了变量s
的列表类型。s = reversed(s)
将变量s
绑定到一个新的迭代器,丢失了原列表。s[:]
的作用:- 不创建新对象,仅修改现有列表的内容。
- 用于切片赋值或创建浅拷贝。
解法5:使用切片
class Solution: def reverseString(self, s: List[str]) -> None: s[:] = s[::-1]
看不懂这个表达
s[:] = s[::-1]
是一种常用的 Python 表达方式,用于原地反转列表。要理解它的行为,需要逐步拆解:
1. s[::-1]
的含义
s[::-1]
是一个切片操作,表示对序列s
按步长-1
进行切片。- 步长为 -1 的意思是:从序列的末尾向前提取元素,从而创建一个新的反转后的序列。
示例:
s = [1, 2, 3, 4] print(s[::-1]) # 输出:[4, 3, 2, 1]
说明:
s[::-1]
创建了一个反转后的新列表,不会修改原列表s
。- 这个新列表与原列表内容相同,但顺序相反。
2. s[:]
的含义
s[:]
是一个切片赋值操作,用于修改列表的所有元素,而不改变列表对象本身。
示例:
s = [1, 2, 3] s[:] = [4, 5, 6] # 修改 s 的内容 print(s) # 输出:[4, 5, 6]
3. s[:] = s[::-1]
的整体含义
s[::-1]
:创建了一个新的反转列表,但没有改变原列表s
。s[:]
:表示“修改原列表的内容,而不改变其对象引用”。- 赋值操作:将
s[::-1]
中反转后的内容,逐一赋值给原列表s
。
运行过程:
- 原列表
s
的元素被替换为s[::-1]
中的内容。 - 原列表
s
依然指向相同的内存地址,但其内容已经被反转。
示例:
s = [1, 2, 3, 4] s[:] = s[::-1] print(s) # 输出:[4, 3, 2, 1]
4. 为什么不用 s = s[::-1]
?
- 如果使用
s = s[::-1]
,s
将被重新绑定到一个新的反转列表。 - 原来的列表
s
不会被修改,而是丢失了引用。
示例:
s = [1, 2, 3, 4] s = s[::-1] # s 现在指向新列表 print(s) # 输出:[4, 3, 2, 1]
5. 总结
表达式 | 含义 | 是否修改原列表 |
---|---|---|
s[::-1] |
创建一个新的反转列表,原列表不变 | 否 |
s[:] = s[::-1] |
原地反转:修改原列表内容,保持同一对象 | 是 |
s = s[::-1] |
将变量 s 绑定到一个新的反转列表,原列表被覆盖 |
否 |
如果需要在不改变列表引用的情况下反转列表,推荐使用 s[:] = s[::-1]
或 .reverse()
方法:
s = [1, 2, 3, 4] s.reverse() # 原地反转,等价于 s[:] = s[::-1] print(s) # 输出:[4, 3, 2, 1]
解法6:使用列表推导
class Solution: def reverseString(self, s: List[str]) -> None: s[:] = [s[i] for i in range(len(s)-1, -1, -1)]
看不懂这个表达
表达式 s[:] = [s[i] for i in range(len(s)-1, -1, -1)]
的作用是原地反转列表 s
的内容。让我们逐步解析其含义。
1. 外层结构:s[:] = ...
s[:]
是切片赋值,表示修改列表s
的内容,而不是创建新的列表。- 任何赋值操作(例如
s[:] = ...
)都会替换列表的所有元素,而不会改变列表对象本身的引用。
2. 内层列表生成式:[s[i] for i in range(len(s)-1, -1, -1)]
这是一个列表推导式,用于生成反转后的列表。以下是它的构成部分:
详细解析:
-
range(len(s)-1, -1, -1)
:range
函数生成从len(s)-1
到0
(包括0
)的递减索引。- 对于一个长度为
n
的列表,索引从n-1
(最后一个元素)开始,到0
(第一个元素)结束,步长为-1
。
-
s[i]
:- 遍历上述索引范围,每次取出对应索引位置的元素。
-
完整列表推导式:
- 将上述步骤结合起来,生成一个反转的列表。
3. 完整过程:s[:] = [s[i] for i in range(len(s)-1, -1, -1)]
- 右侧:通过列表推导式生成一个反转后的新列表。
- 左侧:
s[:]
将反转后的列表内容赋值到原列表s
的每个位置,原列表被修改,但对象引用不变。
特点:
- 与
s[:] = s[::-1]
效果相同。 - 保持了
s
的列表对象不变,只是更新了内容。
5. 对比其他方法
方法 | 描述 | 是否修改原列表 | 是否创建新列表 |
---|---|---|---|
s[:] = s[::-1] |
使用切片反转,原地修改 | 是 | 否 |
s = s[::-1] |
使用切片反转,创建新列表赋值给 s |
否 | 是 |
.reverse() |
原地反转,内置方法 | 是 | 否 |
[s[i] for i in range(len(s)-1, -1, -1)] |
手动实现反转(新列表) | 否 | 是 |
s[:] = [s[i] for i in range(len(s)-1, -1, -1)] |
手动实现原地反转 | 是 | 否 |
6. 总结
表达式 s[:] = [s[i] for i in range(len(s)-1, -1, -1)]
是手动实现列表反转的一种方式,和 s[:] = s[::-1]
效果相同,但更加显式,便于理解反转逻辑的实现。如果需要更简洁的代码,直接使用切片或内置方法更推荐。
解法7:使用reverse()
class Solution: def reverseString(self, s: List[str]) -> None: """ Do not return anything, modify s in-place instead. """ # 原地反转,无返回值 s.reverse()