1:问题描述
来源:LeetCode
难度:中等
问题详情:
给你一个 32 位的有符号整数 x
,返回将 x
中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [ − 2 31 , 2 31 − 1 ] [−2^{31},2^{31} − 1] [−231,231−1] ,就返回 0
。
假设环境不允许存储 64
位整数(有符号或无符号)。
2:问题分析
2.1 时间复杂度和空间复杂度
在真正开始介绍各种算法前,先以表格形式展示各自的时间复杂度和空间复杂度:
算法 | 时间复杂度 | 空间复杂度 |
---|---|---|
列表法 | O ( l o g ( x ) ) O(log(x)) O(log(x)) | O ( l o g ( x ) ) O(log(x)) O(log(x)) |
数学解析法 | O( l o g ( x ) log(x) log(x)) | O ( 1 ) O(1) O(1) |
2.2 列表法
题目本身强调了不允许使用64位的类型,因此当反转后数值超过 32位数能够表达的 [ − 2 31 , 2 31 − 1 ] [−2^{31},2^{31} − 1] [−231,231−1] 范围,也就无法直接使用int类型表达。这是比较麻烦的一点,否则可以直接使用转为字符串类型,然后再反向切片然后转为int即可。
- 使用列表从低位到高位依次存储x中的每一位的数值。
- 然后使用同样的操作存储界限值
- 首先比较两者的长度,如果x对应的反转列表长度较短,直接转换回int类型
- 如果两者长度相等,则从高位到低位依次对比数值大小,如果x对应的数较小,则转换为int类型返回;如果x对应的数较大,说明数值溢出了,返回0.
2.2.1 代码
def reverse(x: int) -> int:
sign = 1
if x < 0:
sign = -1
def get_list(x):
x_list = []
while x:
x, t = divmod(x, 10) # 与 x = x // 10, t= x%10等价
x_list.append(t)
return x_list
def get_int(x_list):
x = 0
for i, item in enumerate(x_list):
x += item * 10 ** (len(x_list) - i - 1)
return x
x = abs(x)
x_list = get_list(x) # 反转后的数值列表,因为转换为列表时出来的就是反的
if sign == 1:
top_list = get_list(2 ** 31)[::-1] # 正常的边界值 + 1
else:
top_list = get_list(2 ** 31 + 1)[::-1]
# 如果长度没有上限的大,那么不用担心这个反转后的数值超过边界值
if len(x_list) < len(top_list):
return get_int(x_list) * sign
# 如果长度相等,那么就需要考虑是否超过界限:
# 如果数值相等,那么说明刚好超过界限,直接返回0
# 如果数值不等,那么就需要依次比较高位的数,如果有反转后的数值有高位大于界限,那么返回0
# 如果高位小于界限,那么直接返回反转后的数值×符号位
# 如果高位相等,则比较后边的高位值
elif len(x_list) == len(top_list):
if x_list == top_list:
return 0
for m, n in zip(x_list, top_list):
if m > n:
return 0
elif m < n:
return get_int(x_list) * sign
对于时间复杂度,因为int类型的数值长度 l = i n t ( log 10 ( x ) ) + 1 l=int(\log _{10}\left( x\right))+1 l=int(log10(x))+1计算得到,所以列表创建和遍历的时间复杂度为 O ( l o g ( x ) ) O(log(x)) O(log(x)),因此该算法的时间复杂度为 O ( l o g ( x ) ) O(log(x)) O(log(x))
空间复杂度同样为 O ( l o g ( x ) ) O(log(x)) O(log(x))
2.3 数学解析法
虽然上述算法通过列表,避免了直接生成可能溢出的结果值,但是列表法的空间复杂度较高。
在介绍数学解析法之前,我们先考虑一个正数的不会溢出的情况。
假设 x = 123 x=123 x=123,那么反转的流程则是:
- 123 / / 10 = 12 , 123 % 10 = 3 123 // 10 = 12, 123 \% 10 = 3 123//10=12,123%10=3
- 0 ∗ 10 + 3 = 3 0*10+3=3 0∗10+3=3
- 12 / / 10 = 1 , 12 % 10 = 2 12//10=1, 12 \% 10=2 12//10=1,12%10=2
- 3 ∗ 10 + 2 = 32 3 * 10 + 2 = 32 3∗10+2=32
- 1 / / 10 = 0 , 1 % 10 = 1 1//10=0,1\%10=1 1//10=0,1%10=1
- 32 ∗ 10 + 1 = 321 32 * 10 + 1=321 32∗10+1=321
代码如下所示:
rev = 0
while x:
x, t = divmod(x, 10) # 上述流程中的1、3、5步骤
rev = rev * 10 + t #上述流程中的2、4、6步骤
同样以正数为例:
如果要保证反转后的数值不溢出,那么就需要最后一次执行的 r e v ∗ 10 + t < = I N T _ M A X rev * 10 + t<= INT\_MAX rev∗10+t<=INT_MAX,这里 I N T _ M A X = 2 31 − 1 INT\_MAX=2^{31}-1 INT_MAX=231−1.
我们将 I N T _ M A X INT\_MAX INT_MAX转换为如下形式:
I N T _ M A X = ⌊ I N T _ M A X 10 ⌋ × 10 + 7 INT\_MAX=\lfloor \dfrac{INT\_MAX}{10}\rfloor ×10 + 7 INT_MAX=⌊10INT_MAX⌋×10+7
那么需要:
r e v ∗ 10 + t < = ⌊ I N T _ M A X 10 ⌋ × 10 + 7 rev * 10 + t<= \lfloor \dfrac{INT\_MAX}{10}\rfloor ×10 + 7 rev∗10+t<=⌊10INT_MAX⌋×10+7
经过简单的变换后:
( r e v − ⌊ I N T _ M A X 10 ⌋ ) ∗ 10 < = 7 − t (rev-\lfloor \dfrac{INT\_MAX}{10}\rfloor) * 10 <= 7-t (rev−⌊10INT_MAX⌋)∗10<=7−t
- 当 r e v > ⌊ I N T _ M A X 10 ⌋ rev>\lfloor \dfrac{INT\_MAX}{10}\rfloor rev>⌊10INT_MAX⌋时,左边的数值 > = 10 >=10 >=10,而右边的 0 = < t < = 9 0=<t<=9 0=<t<=9,因此 7 − t < 10 7-t<10 7−t<10,所以这个不等式是不成立的;
- 当 r e v = ⌊ I N T _ M A X 10 ⌋ rev=\lfloor \dfrac{INT\_MAX}{10}\rfloor rev=⌊10INT_MAX⌋时,左边的数值 = 0 =0 =0,而右边的 0 = < t < = 7 0=<t<=7 0=<t<=7时不等式成立,同时可以考虑下, r e v = ⌊ I N T _ M A X 10 ⌋ rev=\lfloor \dfrac{INT\_MAX}{10}\rfloor rev=⌊10INT_MAX⌋,这个 x x x的长度应该和 I N T _ M A X INT\_MAX INT_MAX是相同的,而最后一次的 t t t则是 x x x的最高位,根据题意其是 t = 1 , 2 t=1,2 t=1,2的,所以这个等式是必然成立的。
- 当 r e v < ⌊ I N T _ M A X 10 ⌋ rev<\lfloor \dfrac{INT\_MAX}{10}\rfloor rev<⌊10INT_MAX⌋时,左边的数值 < = − 10 <=-10 <=−10,而右边的 0 = < t < = 9 0=<t<=9 0=<t<=9,因此 − 2 = < 7 − t < = 7 -2=<7-t<=7 −2=<7−t<=7,所以这个不等式是成立的;
因此通过以上的分析,当 r e v < = ⌊ I N T _ M A X 10 ⌋ rev<=\lfloor \dfrac{INT\_MAX}{10}\rfloor rev<=⌊10INT_MAX⌋时,反转后的数值不会溢出;反之,当 r e v > ⌊ I N T _ M A X 10 ⌋ rev>\lfloor \dfrac{INT\_MAX}{10}\rfloor rev>⌊10INT_MAX⌋时,反转后的数值会溢出。
2.3.1 代码
因为 I N T _ M A X = 2 31 − 1 INT\_MAX = 2 ^{31} -1 INT_MAX=231−1, 而 x x x的下限 I N T _ M I N = − 2 31 INT\_MIN=-2^{31} INT_MIN=−231.
而 ⌊ I N T _ M A X 10 ⌋ = ⌊ ∣ I N T _ M I N ∣ 10 ⌋ \lfloor \dfrac{INT\_MAX}{10}\rfloor=\lfloor \dfrac{|INT\_MIN|}{10}\rfloor ⌊10INT_MAX⌋=⌊10∣INT_MIN∣⌋,所以我们直接使用 r e v > ⌊ 2 31 10 ⌋ rev>\lfloor \dfrac{2^{31}}{10}\rfloor rev>⌊10231⌋作为数值溢出的判断条件。
同时首先得到符号值,然后取绝对值,将正数和负数的情况统一为正数情况处理。
def reverse2(x: int) -> int:
rev = 0
sign = 1
max_rev = 2 ** 31 // 10
if x < 0:
sign = -1
x = abs(x)
while x:
if rev > max_rev:
return 0
x, t = divmod(x, 10)
rev = rev * 10 + t
return rev * sign
时间复杂度同样是 O ( l o g ( x ) ) O(log(x)) O(log(x)),而空间复杂度则只是 O ( 1 ) O(1) O(1)级别的。