「LeetCode」7.整数反转

1,219 阅读6分钟

题目描述🌍

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

如果反转后整数超过 32 位的有符号整数的范围 [231x2311][-2^{31} \leq x \leq 2^{31} - 1] ,就返回 0。

假设环境不允许存储 64 位整数(有符号或无符号)。

示例 1

输入:x = 123
输出:321

示例 2

输入:x = -123
输出:-321

示例 3:

输入:x = 120
输出:21

示例 4:

输入:x = 0
输出:0

提示

  • 231x2311-2^{31} \leq x \leq 2^{31} - 1

异常处理机制🚀

解题思路

提到反转,我们首先会想到库函数 reverse() 方法将字符串反转,但题目是对整数进行反转,所以我们需要将整数转换为字符串类型再操作。

🔮其次需要解决的是反转后的可能出现的整数溢出问题,当赋值给 int 类型的值溢出时,Java 和 C++ 会分别抛出 NumberFormatException/out_of_range 异常,既然这是个异常,那么就顺着题目 ”当反转后溢出返回 0“ 的要求,在捕获异常后不进行多余的处理,直接返回 0 即可,这不失为一个好方法。

Tip:有些数字可能是合法范围内的数字,但是反转过来就超过范围了。假设有 2147483643 这个数字,它是小于最大的 32 位整数 2147483647 的,但是将这个数字反转过来后就变成了 3463847412,这就比最大的 32 位整数还要大了,这样的数字是没法存到 int 里面的,所以肯定要返回 0(因为溢出)。

代码

Java

class Solution {
    public int reverse(int x) {
        // 判断x的正负性
        boolean flag = x < 0 ? true : false;
        
        StringBuilder reverse = new StringBuilder(Integer.toString(Math.abs(x))).reverse();
        try {
            Integer value = Integer.valueOf(reverse.toString());
            // 根据x的正负性返回相应的反转后的整数
            return flag ? -value : value;
        } catch (NumberFormatException e) {
            // Do nothing
        }
        return 0;
    }
}

C++

class Solution {
public:
    int reverse(int x) {
        bool isNegative = x < 0 ? true : false;
        // to_string(): int => string
        string strX = to_string(x);
        reverse(strX.begin(), strX.end());

        try {
            // stoi(): string => int
            int value = stoi(strX);
            // 若正确转换(无异常), 则可以返回
            return isNegative ? -value : value;
        } catch (const out_of_range &e) {
            // cout << e.what() << endl;
        }
        // NULL在C++中就是0
        // return NULL;
        return 0;
    }
};

皆因 reverse() 反转函数

时间复杂度:O(n)O(n)

空间复杂度:O(n)O(n)

数学方法🚀

解题思路

首先我们想一下,怎么去反转一个整数?用栈?或者把整数变成字符串,再去反转这个字符串? 这两种方式是可以,但并不好。实际上我们只要能拿到这个整数的 “末尾数字” 就可以了。 以 12345 为例,先拿到 5,再拿到 4,之后是 3,2,1,我们按这样的顺序就可以反向拼接出一个数字了,也就能达到 “反转” 的效果。 怎么拿末尾数字呢?好办,用取模运算就可以了。

这么看起来,好像一个循环就搞定了,但是我们这里只考虑了正数,再考虑到负数,我们循环的终止条件就不是 x>0 ,而是 x!=0 了。 这样一来,无论正负,最后一次除法运算都会让其变成 0,且对于 12300 这样的数字,也可以完美地解决掉!

看起来这道题就这么解决了,但请注意,题目上还有这么一句:

反转后整数超过 32 位的有符号整数的范围 [231x2311][-2^{31} \leq x \leq 2^{31} - 1] ,就返回 0。

隐含:有些数字可能是合法范围内的数字,但是反转过来就超过范围了。例如上文提到的数字 2147483643。这样的数字反转后没法存到 int 里面的,所以肯定需要提前判断,而不是等到最后一次赋值时出现溢出再处理(此时已报异常,来不及处理);所以,我们到【最大数的1/10】即末尾的前一位时,就要开始判断了:

如果某个数字大于 214748364 ,那后面就不用再判断了,肯定溢出了。

如果某个数字小于 -214748364 ,那后面就不用再判断了,肯定溢出了。

但如果某个数字等于 214748364-214748364 ,肯定是不会溢出的,无需再判断:

  • 以正数为例:其最后一位数必须大于 7 才会造成溢出,但这是在反转后判断的,在反转数字前,该数字必是以 >7 的开头且位数等于 Integer.MAX_VALUE ,所以传形参时必然溢出,而实参是不会给出大于 int 类型的数(否则还没反转就异常了);
  • 负数同理。

代码

Java

class Solution {
    public int reverse(int x) {
        int remainder;
        int res = 0;
        while (x != 0) {
            if ((res > Integer.MAX_VALUE / 10) || (res < Integer.MIN_VALUE / 10)) {
                return 0;
            }
            remainder = x % 10;
            res = res * 10 + remainder;
            x /= 10;
        }
        return res;
    }
}

C++

class Solution {
public:
    int reverse(int x) {
        int ret = 0;
        int remainder;
        while (x != 0) {
            if ((ret > INT32_MAX / 10) || (ret < INT32_MIN / 10)) {
                return 0;
            }
            remainder = x % 10;
            ret = ret * 10 + remainder;
            x /= 10;
        }
        return ret;
    }
};

时间复杂度:O(n)O(n),n 为 x 的位数

空间复杂度:O(1)O(1)

类型转换🚀

解题思路

优雅永不过时!

该题的难点就是解决溢出问题,我们换种思路:为什么一开始就用 int 型变量接收呢?为什么不直接用 long 型变量接收该数,等到返回时进行强制转换为 int 型再处理?这样一来,我们就不用在 while 语句中不断进行 if 条件的判断了。

long 型向 int强制转换可能出现数据溢出的现象,从而导致前后数据不一;据此原理来设计 return 语句,顺势完成题目反转整数溢出返 0 的要求。

代码

Java

class Solution {
    public int reverse(int x) {
        long n = 0;
        while (x != 0) {
            n = n * 10 + x % 10;
            x = x / 10;
        }
        return (int) n == n ? (int) n : 0;
    }
}

C++

class Solution {
public:
    int reverse(int x) {
        long ret = 0;
        while (x != 0) {
            ret = ret * 10 + x % 10;
            x /= 10;
        }
        return (int) ret == ret ? int(ret) : 0;
    }
};

时间复杂度:O(n)O(n),n 为 x 的位数

空间复杂度:O(1)O(1)

知识点🌓

Java

1. char[]StringInteger 互转

ClassModifier and TypeMethod and Description
Integerstatic StringtoString(int i):Integer —— String
Integerstatic IntegervalueOf(String s):String —— Integer
Stringchar[]toCharArray():String —— char[]
Stringstatic StringvalueOf(char[] data):char[] —— String

2. 字符串反转的四种方法:

参考博客:blog.csdn.net/qq_43701760…

C++

1. to_string() 库函数能将各种类型转换为 string 类型

浮点型可能无法正确转换

2. stringintstoi() 库函数

还有其他类似: stol()stoll()

3. 关于 C++ 的 NULLnullptr

⭐结论:

C 中的 NULL 是空指针 void *,而 C++ 中的 NULL 是 0.

所以 C++ 引入了 nullptr 这一新关键字来定义空指针,NULL 只能沦为 0.

编译器提供的头文件所做的处理:

#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif

C 的编译器是 gcc

C++ 的编译器是 g++

❤该问题的详细解答见:C++ 中 NULLnullptr 的区别

4. 反向迭代器 (rbegin, rend) 与 (begin, end) 之间的区别

  • begin() 指向容器的第一个元素
  • end() 指向容器的最后一个元素的下一个位置
  • rbegin() 指向容器的最后一个元素
  • rend() 指向容器的第一个元素的前一个位置

结合图片食用,理解效果更佳

⭐简单示例:

std::vector<int> vec = {1, 2, 3, 4, 5, 6};
// 正向迭代器
std::vector<int>::iterator it;
// 结果: 1 2 3 4 5 6
for (it = vec.begin(); it != vec.end(); ++it) {
    cout << *it << " ";
}

cout << endl;

// 逆向迭代器
std::vector<int>::reverse_iterator r_it;
// 结果: 6 5 4 3 2 1 
for (r_it = vec.rbegin(); r_it != vec.rend(); ++r_it) {
    cout << *r_it << " ";
}

最后🌅

该篇文章为 「LeetCode」 系列的 No.2 篇,在这个系列文章中:

  • 尽量给出多种解题思路
  • 提供题解的多语言代码实现
  • 记录该题涉及的知识点

👨‍💻争取做到 LeetCode 每日 1 题,所有的题解/代码均收录于 「LeetCode」 仓库,欢迎随时加入我们的刷题小分队!