来源:LeetCode
难度:中等
问题详情:
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围
[
−
2
31
,
2
31
−
1
]
[−2^{31},2^{31} − 1]
[−231,231−1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
在真正开始介绍各种算法前,先以表格形式展示各自的时间复杂度和空间复杂度:
| 算法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 列表法 | 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) |
题目本身强调了不允许使用64位的类型,因此当反转后数值超过 32位数能够表达的 [ − 2 31 , 2 31 − 1 ] [−2^{31},2^{31} − 1] [−231,231−1] 范围,也就无法直接使用int类型表达。这是比较麻烦的一点,否则可以直接使用转为字符串类型,然后再反向切片然后转为int即可。
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))
虽然上述算法通过列表,避免了直接生成可能溢出的结果值,但是列表法的空间复杂度较高。
在介绍数学解析法之前,我们先考虑一个正数的不会溢出的情况。
假设
x
=
123
x=123
x=123,那么反转的流程则是:
代码如下所示:
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⌋时,反转后的数值不会溢出;反之,当 r e v > ⌊ I N T _ M A X 10 ⌋ rev>\lfloor \dfrac{INT\_MAX}{10}\rfloor rev>⌊10INT_MAX⌋时,反转后的数值会溢出。
因为
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)级别的。