LeetCode 第7题:整数反转(Python3解法)

1:问题描述

来源:LeetCode

难度:中等


问题详情:
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。

如果反转后整数超过 32 位的有符号整数的范围 [ − 2 31 , 2 31 − 1 ] [−2^{31},2^{31} − 1] [2312311] ,就返回 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] [2312311] 范围,也就无法直接使用int类型表达。这是比较麻烦的一点,否则可以直接使用转为字符串类型,然后再反向切片然后转为int即可。

  1. 使用列表从低位到高位依次存储x中的每一位的数值。
  2. 然后使用同样的操作存储界限值
  3. 首先比较两者的长度,如果x对应的反转列表长度较短,直接转换回int类型
  4. 如果两者长度相等,则从高位到低位依次对比数值大小,如果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,那么反转的流程则是:

  1. 123 / / 10 = 12 , 123 % 10 = 3 123 // 10 = 12, 123 \% 10 = 3 123//10=12,123%10=3
  2. 0 ∗ 10 + 3 = 3 0*10+3=3 010+3=3
  3. 12 / / 10 = 1 , 12 % 10 = 2 12//10=1, 12 \% 10=2 12//10=1,12%10=2
  4. 3 ∗ 10 + 2 = 32 3 * 10 + 2 = 32 310+2=32
  5. 1 / / 10 = 0 , 1 % 10 = 1 1//10=0,1\%10=1 1//10=0,1%10=1
  6. 32 ∗ 10 + 1 = 321 32 * 10 + 1=321 3210+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 rev10+t<=INT_MAX,这里 I N T _ M A X = 2 31 − 1 INT\_MAX=2^{31}-1 INT_MAX=2311.

我们将 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 rev10+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 rev10INT_MAX10<=7t

  • 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 7t<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=<7t<=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=2311, 而 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=10INT_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)级别的。

猜你喜欢

转载自blog.csdn.net/weixin_43490422/article/details/126545270