7.整数反转




7. 整数反转
二哥的 LeetCode 题解真的精辟——鲁迅(我没说)
题意
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
示例
输入:x = 123
输出:321
输入:x = -123
输出:-321
输入:x = 120
输出:21
输入:x = 0
输出:0
难度
中等
分析 1
看到这题,我的第一感觉就是把 32 位的整数看成是一个字符串,然后直接调用
StringBuilder 的 reverse 方法,这样就可以完成反转了,对吧,完全可行。
剩下需要做的,就是处理一下负数的情况,还有超出边界的情况。
class Solution {
public int reverse(int x) {
String reversed = new
StringBuilder().append(Math.abs(x)).reverse().toString();
try {
// 如果原始整数为负数,则反转后的结果也应该是负数
int result = Integer.parseInt(reversed);
return (x < 0) ? result * -1 : result;
} catch (NumberFormatException e) {
// 捕获溢出异常并返回 0
return 0;
}
}
}
①、Math.abs() 方法可以获取一个数的绝对值,比如 Math.abs(-123)的结果就是 123。
②、然后用 StringBuilder 的 append 方法将正数添加到字符串序列中。
③、调用 reverse 方法将字符串反转。我在《二哥的 Java 进阶之路》里曾讲过 reverse 方
法,没看过源码的球友可以回头看一眼。
int n = count - 1; // 字符序列的最后一个字符的索引
// 遍历字符串的前半部分,`(n-1) >> 1` 是 `(n-1) / 2` 的位运算表示,也就是字符串的前半部
分的最后一个字符的索引。
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j; // 计算相对于 j 对称的字符的索引
char cj = value[j]; // 获取当前位置的字符
char ck = value[k]; // 获取对称位置的字符
value[j] = ck; // 交换字符
value[k] = cj; // 交换字符
}
这个翻转的方法其实非常巧妙,从字符串的两端开始交换,直到中间位置,比从头到尾或
者从尾到头要快一倍。
④、最后调用 Integer 的 parseInt 方法将字符串转换成整数,如果超出边界,就会抛出
NumberFormatException 异常,我们通过 try-catch 捕获这个异常,然后返回 0。如果是
负数,就将结果乘以 -1。
来看一下题解的效率:
007.%E6%95%B4%E6%95%B0%E5%8F%8D%E8%BD%AC-20231228125411.png
还不错,那其实我们可以再优化一下,不调用 StringBuilder 的 reverse 方法,我们可以自
己实现一个翻转的方法,把 reverse 方法的源码拿过来改一改就行了。
class Solution {
public int reverse(int x) {
String reversed = reverseString(String.valueOf(Math.abs(x)));
try {
// 如果原始整数为负数,则反转后的结果也应该是负数
int result = Integer.parseInt(reversed);
return (x < 0) ? result * -1 : result;
} catch (NumberFormatException e) {
// 捕获溢出异常并返回 0
return 0;
}
}
private String reverseString(String s) {
char[] value = s.toCharArray();
int n = value.length - 1;
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
char cj = value[j];
char ck = value[k];
value[j] = ck;
value[k] = cj;
}
return new String(value);
}
}
只不过,这样做的意义不大,换汤不换药而已,有没有更好的方法呢?
分析 2
对于一个整数来说,我们可以通过取余和除法来获取它的每一位,比如 123,我们通过
123 % 10 来获取它的个位 3,123 / 10 来把它的个位去掉,剩下 12;
然后通过 12 % 10 来获取它的十位 2,12 / 10 来把它的十位去掉,剩下 1;
然后再通过 1 % 10 来获取它的百位 1,1 / 10 来把它的百位去掉,剩下 0,这样就把整数
的每一位都获取到了,获取知道就很好处理了,不断地乘以 10 累加就行了。
007.%E6%95%B4%E6%95%B0%E5%8F%8D%E8%BD%AC-20231228132251.png
唯一需要注意的就是判断一下是否超出边界,这里我用了一个小技巧,就是在每次累加之
前,先判断一下累加后的结果是否超出边界,如果超出边界,就直接返回 0,否则再累
加。
class Solution {
public int reverse(int x) {
int rev = 0; // 用于存储反转后的结果
while (x != 0) {
int pop = x % 10; // 获取 x 的最后一位数字
x /= 10; // 移除 x 的最后一位数字
// 检查溢出:如果 rev > Integer.MAX_VALUE/10 或 rev <
Integer.MIN_VALUE/10,则会溢出
if (rev > Integer.MAX_VALUE / 10 || (rev == Integer.MAX_VALUE / 10 &&
pop > 7)) return 0;
if (rev < Integer.MIN_VALUE / 10 || (rev == Integer.MIN_VALUE / 10 &&
pop < -8)) return 0;
rev = rev * 10 + pop; // 将 pop 添加到 rev 的最后
}
return rev; // 返回反转后的整数
}
}
①、rev == Integer.MAX_VALUE / 10 && pop > 7
• Integer.MAX_VALUE 是 2147483647,Integer.MAX_VALUE / 10 则是
214748364。
• 当 rev 等于 214748364 时,乘以 10 再加上任何大于 7 的数字将导致整数溢出,比
如说 214748364 * 10 + 8 = 2147483648 > Integer.MAX_VALUE(2147483647)。
②、rev == Integer.MIN_VALUE / 10 && pop < -8
• Integer.MIN_VALUE 是 -2147483648,Integer.MIN_VALUE / 10 则是
-214748364。
• 当 rev 等于 -214748364 时,乘以 10 再减去任何小于 -8 的数字将导致整数溢出,比
如说 -214748364 * 10 - 9 = -2147483649 < Integer.MIN_VALUE(-2147483648)。
那如果说,写题解的时候,不想出现这种魔法数字(7 或者 -8),可以这样写:
class Solution {
public int reverse(int x) {
int rev = 0; // 用于存储反转后的结果
while (x != 0) {
int pop = x % 10; // 获取 x 的最后一位数字
x /= 10; // 移除 x 的最后一位数字
// 检查溢出:如果 rev > Integer.MAX_VALUE/10 或 rev <
Integer.MIN_VALUE/10,则会溢出
if (rev > Integer.MAX_VALUE / 10 || (rev == Integer.MAX_VALUE / 10 &&
pop > Integer.MAX_VALUE % 10)) return 0;
if (rev < Integer.MIN_VALUE / 10 || (rev == Integer.MIN_VALUE / 10 &&
pop < Integer.MIN_VALUE % 10)) return 0;
rev = rev * 10 + pop; // 将 pop 添加到 rev 的最后
}
return rev; // 返回反转后的整数
}
}
运行效率也非常喜人,直接击败 100% 的 Java 提交者。
007.%E6%95%B4%E6%95%B0%E5%8F%8D%E8%BD%AC-20231228132440.png
总结
这道题其实算不上中等的难度,不论是直接调用 reverse 还是通过取余和除法来获取每一
位,都是比较容易想到的方法,唯一需要考虑的就是边界问题。
力扣链接:https://leetcode.cn/problems/reverse-integer/submissions/
学过基本数据类型的球友应该知道,Java 中的整数类型有 4 种,分别是 byte、short、int
和 long,它们的取值范围如下:
类型
byte
short
int
long
其中 int 类型的取值范围就是 32 位的有符号整数的取值范围,所以我们可以把 int 类型的
最大值和最小值作为边界来判断是否溢出。
32 位的有符号整数的取值范围是从 -2^31 到 2^31 - 1,其中一位用于表示符号(正或
负),剩下的 31 位用于表示数值,这意味着其范围是 -2,147,483,648(即 -2^31)到
2,147,483,647(即 2^31 - 1)。
在二进制系统中,每个位(bit)可以表示两个状态,通常是 0 和 1。对于 32 位得正二进
制数,除去符号位,从右到左的每一位分别代表 2^0, 2^1, 2^2, …, 2^30,这个二进制数转
换为十进制就是 2^0 + 2^1 + 2^2 + … + 2^30,也就是 2,147,483,647。
第一种题解很容易想到,涉及到的知识点有:
• StringBuilder
• String
• 基本数据类型的包装器
• 异常处理
第二种题解也不难想到,主要就是除法和取余的运算,以及边界的判断,涉及到的知识点
有:
• 基本数据类型
• 运算符
• 流程控制