Description
tags: Math
Given a 32-bit signed integer, reverse digits of an integer.
Example:
Input: 123 Output: 321
Input: -123 Output: -321
Input: 120 Output: 21
Note:
Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−2^31, 2^31 − 1]. For the purpose of this problem, assume that your function returns 0 when the reversed integer overflows.
Solution
class Solution {
public:
int reverse(int x) {
int ret = 0;
if (x == 0) return x;
while (x != 0) {
int digit = x % 10;
int newResult = ret * 10 + digit;
// 替身变量还原法,检查乘法溢出
if ((newResult - digit) / 10 != ret)
return 0;
ret = newResult;
// 方法结束
x /= 10;
}
return ret;
}
};
Analysis
需要处理三个点:
- 负数的情况
- 末尾的零
- 溢出检查(Overflow)
1)不能使用 int整数*10 > Integer.MAX_VALUE 这样的if判断,因为乘法后已经溢出,判断已经没有意义了。
2)使用已做操作的还原步骤检查溢出(从“数值内容”上检查)
前两种情况其实都可以被算法涵盖,无需单独讨论。最后一种情况需要重点关注。
- Time Complexity: O(logx). There are roughly log 10(x) digits in x.
- Space Complexity: O(1).
Knowledge
补码
计算机中数值采用补码
表示。
使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。
32-bits int型:首位符号位(0表示正数,1表示负数),其余位表示数值。
- 正数的补码和原码相同
- 负数的补码为原码的数值位取反加一。
-1
原码:1000 0000 0000 0001
取反:1111 1111 1111 1110
加1:1111 1111 1111 1111
(此即为补码)
int 的取值范围:-2^31 ~ (2^31-1)
INT_MIN是-2^31
的原因是:规定了1000 0000 0000 0000
这个数表示-2^31
(本来是-0),所以负数多出来一个。
C++中int最大值表示为INT_MAX
,int最小值表示为INT_MIN
。
模除运算
取模运算(又称模除,求模,modulus,modulo),a mod n
C++中的取模运算
- 同号取模(同正或同负结果相同)
5 % 2 = 1
9 % 3 = 0
(-5) % (-2) = 1 - 异号取模。
被除数和除数有一个为负数:余数符号与被除数相同
-5 % 2 = -1 (5 % 2 = 1,-5为负数,所以结果为-1)
取模与取余
取余,遵循尽可能让商向0靠近的原则
取模,遵循尽可能让商向负无穷靠近的原则
符号相同时,两者不会冲突。比如,7 / 3 = 2.3
,产生了两个商2和3。7 = 3 * 2 + 1
或7 = 3 * 3 + (-2)
。取模和取余都选择2为商,因此,7 rem 3 = 1
,7 mod 3 = 1
。
符号不同时,两者会产生冲突。比如,7 / (-3) = -2.3
,产生了两个商-2和-3。7 = (-3) * (-2) + 1
或7 = (-3) * (-3) + (-2)
。因此,7 rem (-3) = 1
,7 mod (-3) = (-2)
归纳为
对于整型数a,b来说,取余和取模的运算方法为:
1、求整数商:c = a / b;
2、取余和取模:r = a - c * b;
当a和b符号一致时,求模运算和求余运算所得的c的值一致,因此结果一致。
当符号不一致时,求模运算结果的符号和除数b一致,求余运算结果的符号和被除数a一致。
所以,C++中的%
运算,其实是取余运算。
整型溢出
- 什么是整型溢出
只有同号整型的相加有可能出现溢出(overflow)。
假设用k个字节表示一个整型变量, 那么这个变量可以表示的有符号整数的范围是-2^(8k-1) ~ 2^(8k-1) – 1
。两个正整数或者两个负整数相加就有可能超过这个整型变量所能表示的范围,向上超出>2^(8k-1) – 1
我们称之为向上溢出,向下超出<-2^(8k-1)
, 我们称之为向下溢出。 注意这里两个整数符号相同是整型相加溢出(overflow)的必要条件,也就是说只有符号相同的两个整数相加才有可能产生溢出(overflow)问题。
1) unsigned无符号整型溢出
对于unsigned整型溢出,C的规范是有定义的——“溢出后的数会以2^(8*sizeof(type))
作模运算”,也就是说,如果一个unsigned char(1字符,8 bits)溢出了,会把溢出的值与256求模。
unsigned char x = 0xff;
printf("x: %d", ++x);
上面的代码会输出:0 (因为0xff + 1是256,与2^8求模后就是0)
2)signed有符号整型溢出
对于signed整型的溢出,C的规范定义是“undefined behavior”,也就是说,编译器爱怎么实现就怎么实现。对于大多数编译器来说,算得啥就是啥。
char x = 0x7f;
printf("x: %d", ++x);
上面的代码会输出:-128,因为0x7f + 0x01得到0x80,也就是二进制的1000 0000,符号位为1,负数,后面为全0,就是负的最小数,即-128。signed 整型溢出有可能是负数也有可能是正数。
整型溢出的危害
1)整型溢出导致死循环
2)整形转型时的溢出
3)内存分配
4)缓冲区溢出导致安全问题
5)size_t 的溢出整型溢出的检查
“在整型溢出之前,一定要做检查,不然,就太晚了”。
int AddInt(int a, int b) {
if (a > 0 && b > 0 && a > INT_MAX - b) throw overflow_exception;
if (a < 0 && b < 0 && a < INT_MIN - b) throw overflow_exception;
return (a + b);
}
// for multiplication
#include <limits.h>
int a = <something>;
int x = <something>;
if (a > INT_MAX / x) /* `a * x` would overflow */;
if ((a < INT_MIN / x)) /* `a * x` would underflow */;
// there may be need to check for -1 for two's complement machines
if ((a == -1) && (x == INT_MIN)) /* `a * x` can overflow */
if ((x == -1) && (a == INT_MIN)) /* `a * x` (or `a / x`) can overflow */
除了INT_MIN和-1特殊情况外,不可能超过INT_MIN或INT_MAX。
二分查找中的溢出
计算mid = (low + high) / 2
有可能发生溢出。应为
int mid = low + (high - low) / 2;