题目
给出一个 32
位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
注意:
假设我们的环境只能存储得下 32
位的有符号整数,则其数值范围为
[
−
2
31
,
2
31
−
1
]
[−2^{31}, 2^{31} − 1]
[−231,231−1], 请根据这个假设,如果反转后整数溢出那么就返回
0
0
0。
函数原型
C的函数原型:
int reverse( int x ){}
分析:
- 输入参数是整型数字,如
123
- 返回值也是整型数字, 如
321
边界条件
int reverse( int x ){
if( x < -2147483648 || x > 2147483647 )
return 0;
// 边界判断,-2147483648是 2的31次方,或者写为 INT_MIN、INT_MAX
}
不过,输入参数x并不需要判断溢出,因为 x 是一个的范围始终在 [ − 2 31 , 2 31 − 1 ] [−2^{31}, 2^{31} − 1] [−231,231−1]。
所以,这个边界判断应该是用在整数反转之后的。
比如 x = 2147483648
时,刚好没溢出,但翻反后 8463847412
显然是溢出了。
算法设计:模拟法
我们可以分成三步实现:
-
整数转字符
-
使用栈或数组或递归来翻转字符
-
字符转整数
整数转字符、字符转整数,在 C 语言里很方便。
-
sscanf() 函数,可以把字符转其他任何类型, 如整型;
-
sprintf() 函数,可以其他类型(如整型)转字符;
具体用法:
// 字符转整数
char str[32+1] = "123";
int num = 0;
sscanf(str, "%d", &num);
// 整数转字符
int num = 123;
char str[32+1] = {'\0'};
sprintf(str, "%d", num);
第一、三步都知道怎么实现了,再用数组翻转字符,那大概就完成了。
实现数组翻转:
// 方案一:用俩个数组翻转的方法,但多了一个空间复杂度n
int i, j;
char res_str[33] = { '\0' };
for (i = len - 1, j = 0; i < -1, j < len; --i, ++j)
res_str[i] = str[j];
// 方案二:原地翻转,就只需要一个变量而已
int tmp;
for (i = 0; i < len / 2; i++)
{
tmp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = tmp;
}
思路就是这样,但魔鬼在细节,还有许多东西没考虑进来。
int reverse(int x)
{
// 第一步,整数转字符
char str[33] = { '\0' };
sprintf(str, "%d", x);
// 第二步,用数组翻转字符 -> 先得到字符长度(整数多少位) -> 用数组翻转字符
int len = strlen(str);
int tmp;
for (int i = 0; i < len / 2; i++)
{
tmp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = tmp;
}
// 第三步,字符转整数
int n;
sscanf(str, "%ld", &n);
return n;
}
还有许多东西没考虑进来。
比如,负数的问题。
为了处理负数,我们加一个标志位 flag。
// 如果 x 是负数,那flag = -1
int flag=0;
if ( x < 0 )
flag = -1;
x = -x;
又比如,翻转后可能会溢出。
if( n < -2147483648 || n > 2147483647 )
return 0;
结合在一起:
int reverse(int x)
{
// 负数处理
int flag = 0;
if (x < 0)
flag = -1;
// 第一步,整数转字符
char str[33] = { '\0' };
sprintf(str, "%d", x);
// 第二步,数组翻转字符
int len = strlen(str);
// 用数组翻转字符
int tmp;
for (int i = 0; i < len / 2; i++)
{
tmp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = tmp;
}
// 第三步,字符转整数
int n;
sscanf(str, "%ld", &n);
if (flag == -1)
n = -n;
// 边界判断
if (n < -2147483648 || n > 2147483647)
return 0;
return n;
}
好,自己测试一遍:
int reverse(int x)
{
// 负数处理
int flag = 0;
if (x < 0)
flag = -1;
// 第一步,整数转字符
char str[33] = { '\0' };
sprintf(str, "%d", x);
// 第二步,数组翻转字符
int len = strlen(str);
// 先得到字符长度(整数多少位)
// 用数组翻转字符
int tmp;
for (int i = 0; i < len / 2; i++)
{
tmp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = tmp;
}
// 第三步,字符转整数
int n;
sscanf(str, "%d", &n);
if (flag == -1)
n = -n;
// 边界判断
if (n < -2147483648 || n > 2147483647)
return 0;
return n;
}
int main()
{
int n;
scanf("%d", &n);
printf("\n%d\n", reverse(n));
}
上面的测试用例:123、-123、620 输出结果和预期结果一样。
可提交到Leetcode平台上, 1534236469 1534236469 1534236469 这个数字测试通不过。
1534236469
<
2147483647
(
2
31
−
1
)
1534236469 < 2147483647(2^{31}-1)
1534236469<2147483647(231−1),但翻转后的数
9646324351
>
2147483647
9646324351 > 2147483647
9646324351>2147483647,按理说,我们应该 return 0
的呀。
这里怎么输出的是 1056389759 1056389759 1056389759 呢?
找呀找,问题在 sscanf()
函数这里。
// 第三步,字符转整数
int n;
sscanf(str, "%d", &n);
第二步是翻转,
1534236469
1534236469
1534236469 变成了
9646324351
9646324351
9646324351, 而 int n
的最大限度是
2147483647
2147483647
2147483647。
嗯嗯,找到问题点了。
于是,怎么办呢?
改成长整型即可~
// 第三步,字符转整数
long long n;
sscanf(str, "%ld", &n);
完整代码:
int reverse(int x)
{
// 负数处理
int flag = 0;
if (x < 0)
flag = -1;
// 第一步,整数转字符
char str[33] = { '\0' };
sprintf(str, "%d", x);
// 第二步,数组翻转字符
int len = strlen(str);
// 先得到字符长度(整数多少位)
// 用数组翻转字符
int tmp;
for (int i = 0; i < len / 2; i++)
{
tmp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = tmp;
}
// 第三步,字符转整数
long long n;
sscanf(str, "%ld", &n);
if (flag == -1)
n = -n;
// 边界判断
if (n < -2147483648 || n > 2147483647)
return 0;
return n;
}
过程模拟的复杂度:
- 时间复杂度: Θ ( n ) \Theta (n) Θ(n)
- 空间复杂度:
Θ
(
n
)
\Theta (n)
Θ(n)
算法设计:求余法
对于有规律的式子,我们就可以用模拟计算机模拟人的推理。
核心在于模拟上面等式右边的过程。
那我们必须找到规律的不变式、结束条件。
要取某数第 i
位,使用取余数和整除即可分离出每一位。
比如 123
,
- 取
1
,123/100
- 取
2
,123/10%10
- 取
3
,123%10
翻转 123
为 321
,
- 取
3
,(123%10)*100
- 取
2
,((123/10)%10)*10
- 取
1
,123/100
模拟下来:
要取某数第
i
位,使用取余数和整除即可分离出每一位
而后把第
i
位再不断乘左移(是10
进制,所以乘10
)
并加上第
i+1
位的余数。
int num = 0;
while(x != 0){
num = num * 10 + x%10;
x /= 10
}
测试一下吧。
int reverse( int x ){
int num = 0;
while(x != 0){
num = num * 10 + x%10;
x /= 10;
}
return num;
}
AC(通过),那提交了。
哎呦,忘记了还有溢出的。
if( num < -2147483648 || num > 2147483647 )
return 0;
完整版:
int reverse( int x ){
int num = 0;
while(x != 0){
if( num < -2147483648/10 || num > 2147483647/10 )
return 0;
num = num * 10 + x%10;
x /= 10;
}
return num;
}
数学模拟的复杂度:
- 时间复杂度: Θ ( n ) \Theta (n) Θ(n)
- 空间复杂度: Θ ( 1 ) \Theta (1) Θ(1)