問題引入
整數反轉
給出一個 32 位的有符號整數,你需要將這個整數中每位上的數字進行反轉。
示例 1:
輸入: 123
輸出: 321
示例 2:
輸入: -123
輸出: -321
示例 3:
輸入: 120
輸出: 21
注意:假設我們的環境只能存儲得下 32 位的有符號整數,則其數值範圍爲 [−231, 231 − 1]。請根據這個假設,如果反轉後整數溢出那麼就返回 0。
踩坑之路
Too Young Too Simple
思路:使用一個StringBuffer從數字的尾部開始倒着追加數字字符。最後用Integer.parseInt()函數將String轉換成整數,同時還可以去掉0。代碼如下:
import java.util.Scanner;
public class ReverseNum {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
System.out.println(reverse(num));
}
private static int reverse(int x) {
String numStr = String.valueOf(x);
StringBuffer stringBuffer = new StringBuffer();
String res = "";
if (numStr.charAt(0) == '-') {
for (int i = numStr.length() - 1; i >= 1; i--) {
stringBuffer.append(numStr.charAt(i));
}
res = "-" + stringBuffer.toString();
} else {
for (int i = numStr.length() - 1; i >= 0; i--) {
stringBuffer.append(numStr.charAt(i));
}
res = stringBuffer.toString();
}
return Integer.parseInt(res);
}
}
然而:當輸入1234560789,出現瞭如下異常:
Exception in thread "main" java.lang.NumberFormatException: For input string: "9870654321"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:68)
at java.base/java.lang.Integer.parseInt(Integer.java:652)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at com.me.ReverseNum.reverse(ReverseNum.java:27)
at com.me.ReverseNum.main(ReverseNum.java:9)
填坑永遠在路上
根據異常,我們定位到Integer.parseInt()函數上,打開該函數的源碼,如下:
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
繼續往下看:
/**
* Parses the string argument as a signed integer in the radix
* specified by the second argument. The characters in the string
* must all be digits of the specified radix (as determined by
* whether {@link java.lang.Character#digit(char, int)} returns a
* nonnegative value), except that the first character may be an
* ASCII minus sign {@code '-'} ({@code '\u005Cu002D'}) to
* indicate a negative value or an ASCII plus sign {@code '+'}
* ({@code '\u005Cu002B'}) to indicate a positive value. The
* resulting integer value is returned.
*
* <p>An exception of type {@code NumberFormatException} is
* thrown if any of the following situations occurs:
* <ul>
* <li>The first argument is {@code null} or is a string of
* length zero.
*
* <li>The radix is either smaller than
* {@link java.lang.Character#MIN_RADIX} or
* larger than {@link java.lang.Character#MAX_RADIX}.
*
* <li>Any character of the string is not a digit of the specified
* radix, except that the first character may be a minus sign
* {@code '-'} ({@code '\u005Cu002D'}) or plus sign
* {@code '+'} ({@code '\u005Cu002B'}) provided that the
* string is longer than length 1.
*
* <li>The value represented by the string is not a value of type
* {@code int}.
* </ul>
*
* <p>Examples:
* <blockquote><pre>
* parseInt("0", 10) returns 0
* parseInt("473", 10) returns 473
* parseInt("+42", 10) returns 42
* parseInt("-0", 10) returns 0
* parseInt("-FF", 16) returns -255
* parseInt("1100110", 2) returns 102
* parseInt("2147483647", 10) returns 2147483647
* parseInt("-2147483648", 10) returns -2147483648
* parseInt("2147483648", 10) throws a NumberFormatException
* parseInt("99", 8) throws a NumberFormatException
* parseInt("Kona", 10) throws a NumberFormatException
* parseInt("Kona", 27) returns 411787
* </pre></blockquote>
*
* @param s the {@code String} containing the integer
* representation to be parsed
* @param radix the radix to be used while parsing {@code s}.
* @return the integer represented by the string argument in the
* specified radix.
* @throws NumberFormatException if the {@code String}
* does not contain a parsable {@code int}.
*/
public static int parseInt(String s, int radix)
throws NumberFormatException
{
/*
* WARNING: This method may be invoked early during VM initialization
* before IntegerCache is initialized. Care must be taken to not use
* the valueOf method.
*/
if (s == null) { // 如果接受的字符串爲空, 就報空字符串的異常
throw new NumberFormatException("null");
}
if (radix < Character.MIN_RADIX) { // 判斷基數是不是符合要求
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}
if (radix > Character.MAX_RADIX) { // 判斷基數是不是符合要求
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}
boolean negative = false; // 判斷符號
int i = 0, len = s.length(); // 設置初始位置和字符串的長度
int limit = -Integer.MAX_VALUE;
if (len > 0) { // 字符串的長度必須大於零
char firstChar = s.charAt(0); // 獲得字符串的第一個字符
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+') { // 如果不爲+的話就報錯
throw NumberFormatException.forInputString(s, radix);
}
// 字符串的長度爲1但是又不是數字, 那肯定就出錯了
if (len == 1) { // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s, radix);
}
i++;
}
int multmin = limit / radix;
int result = 0;
/*
* 下面的過程其實很好理解, 以8進制的"534"爲例
* * (-5*8-3)*8-4 = -348, 根據符號位判斷返回的是348
*/
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
// 除了前面的判斷這裏的也有點複雜, 因爲要考慮到各種進位
// 這個將i位置上的字符根據基數轉爲實際的值, A->11
int digit = Character.digit(s.charAt(i++), radix);
if (digit < 0 || result < multmin) {
throw NumberFormatException.forInputString(s, radix);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s, radix);
}
result -= digit;
}
return negative ? result : -result; // 根據符號位來判斷返回哪一個
} else {
throw NumberFormatException.forInputString(s, radix);
}
}
函數說明
Integer.parseInt(String s):方法用於將字符串參數作爲有符號的十進制整數進行解析。
如果方法有兩個參數, 使用第二個參數指定的基數,將字符串參數解析爲有符號的整數。
- parseInt(String s): 返回用十進制參數表示的整數值。
- parseInt(String s, int radix):使用指定基數的字符串參數表示的整數 (基數可以是 10, 2, 8, 或 16 等進制數)
其他相關函數
**Integer.valueof(String s):**把字符串s解析成Integer對象類型,返回的integer 可以調用對象中的方法。
**StringUtils.defaultIfBlank(T str, T defaultStr):**如果字符串爲空白符就返回默認字符串defaultStr。可以以string的類型比較數值大小
Integer.parseInt(s)與Integer.valueOf(s)的區別:
Integer.parseInt(s) 多次解析同一個字符串得到的int基本類型數據是相等的,可以直接通過“==" 進行判斷是值是否相等。基本類型不含equals方法。
Integer.parseInt(s) == Integer.parseInt(s)
Integer.valueOf(s) 多次解析相同的一個字符串時,得到的是Integer類型的對象,得到的對象有時是同一個對象,有時是不同的對象,要根據把s字符串解析的整數值的大小進行決定:
- 如果s字符串對應的整數值在 -128~127之間,則解析出的Integer類型的對象是同一個對象;
- 如果s字符串對應的整數值不在-128~127之間,則解析出的Integer類型的對象不是同一個對象。
不管對象是否相等,對象中的value值是相等的。
Integer.parseInt(s) == Integer.parseInt(s)
Integer.parseInt(s).equals(Integer.parseInt(s))
equals是比較的兩個對象value值是否相等;
“==”是比較兩個對象是否相等。
JDK源碼中,由於在-128~127之間的整數值用的比較頻繁,當每次要創建一個value值在-128~127之間的Integer對象時,直接從緩存中拿到這個對象,所以value值相同的Integer對象都是對應緩存中同一個對象。-128~127之外的整數值用的不是太頻繁,每次創建value值相同的Integer對象時,都是重新創建一個對象,所以創建的對象不是同一個對象。
常見異常
看完了源碼,大概知道了什麼情況下會出現異常:
- 如下4種情況會拋 NumberFormatException 異常
- 第一個字符串參數s爲null、或長度爲0的字符串
- 第二個基數參數radix小於MIN_RADIX=2、或大於MAX_RADIX=36
- 字符串s的任何數字都不是指定基數的字符(數字/字母),除如下2種情況:
①第一個字符可以爲減號-
②加號+且長度大於1第一個字符可以爲減號-、或加號+且長度大於1 - 字符串s的值不是int型
- 常見的 NumberFormatException 異常
- s爲空串、空,如:parseInt("")
- s中包括空格,如:parseInt("23 ")
- 10進制時,s中包括字符串,如:parseInt(“a32”)
- s不以-、+、數字開頭、或包含字符串
- s超出int允許的範圍[-2147483648,2147483647],對應到2進制數字:"-10000000000000000000000000000000" (32位數字)~ “1111111111111111111111111111111”(31位數字)
- 其他轉換基數radix時,超出表示範圍
恍然大悟
看到這裏,我們就知道了爲什麼輸入1234560789就會出現NumberFormatException 異常,因爲這個數值已經超出了int所允許的範圍,因此,會拋出異常。
那麼問題來了,如何解決?
正解
採用計算的方式,不再使用字符追加的方式。以12345爲例,先拿到5,再拿到4,之後是3,2,1,我們按這樣的順序就可以反向拼接處一個數字了,也就能達到反轉的效果。怎麼拿末尾數字呢?用取模運算就可以。
過程說明:
1、將12345 % 10 得到5,之後將12345 / 10
2、將1234 % 10 得到4,再將1234 / 10
3、將123 % 10 得到3,再將123 / 10
4、將12 % 10 得到2,再將12 / 10
5、將1 % 10 得到1,再將1 / 10
這麼看起來,一個循環就搞定了,循環的判斷條件是x>0
但這樣做忽略了負數。
循環的判斷條件應該是while(x!=0),無論正數還是負數,按照上面不斷的/10這樣的操作,最後都會變成0,所以判斷終止條件就是!=0
有了取模和除法操作,對於像12300這樣的數字,也可以完美的解決掉了。
看起來這道題就這麼解決了,但請注意,題目上還有這麼一句
假設我們的環境只能存儲得下 32 位的有符號整數,則其數值範圍爲 [−2^31, 2^31 − 1]。
也就是說我們不能用long存儲最終結果,而且有些數字可能是合法範圍內的數字,但是反轉過來就超過範圍了。假設有1147483649這個數字,它是小於最大的32位整數2147483647的,但是將這個數字反轉過來後就變成了9463847411,這就比最大的32位整數還要大了,這樣的數字是沒法存到int裏面的,所以肯定要返回0(溢出了)。
甚至,我們還需要提前判斷
上圖中,綠色的是最大32位整數
第二排數字中,橘子的是5,它是大於上面同位置的4,這就意味着5後跟任何數字,都會比最大32位整數都大。
所以,我們到【最大數的1/10】時,就要開始判斷了
如果某個數字大於 214748364那後面就不用再判斷了,肯定溢出了。
如果某個數字等於 214748364呢,這對應到上圖中第三、第四、第五排的數字,需要要跟最大數的末尾數字比較,如果這個數字比7還大,說明溢出了。
對於負數也是一樣的
上圖中綠色部分是最小的32位整數,同樣是在【最小數的 1/10】時開始判斷
如果某個數字小於 -214748364說明溢出了
如果某個數字等於 -214748364,還需要跟最小數的末尾比較,即看它是否小於8
代碼:
public static int reverse(int x) {
int res = 0;
while (x != 0) {
// 每次取末尾數字
int tmp = x % 10;
// 判斷是否大於最大32位整數
if (res > 214748364 || (res == 214748364 && tmp > 7)) {
return 0;
}
// 判斷是否小於最小32位整數
if (res < -214748364 || (res == -214748364 && tmp < -8)) {
return 0;
}
res = res * 10 + tmp;
x = x / 10;
}
return res;
}
完整代碼
package com.me;
import java.util.Scanner;
public class ReverseNum {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
// System.out.println(reverse0(num));
System.out.println(reverse1(num));
System.out.println(reverse2(num));
System.out.println(reverse3(num));
}
public static int reverse0(int x) {
String numStr = String.valueOf(x);
StringBuffer stringBuffer = new StringBuffer();
String res = "";
if (numStr.charAt(0) == '-') {
for (int i = numStr.length() - 1; i >= 1; i--) {
stringBuffer.append(numStr.charAt(i));
}
res = "-" + stringBuffer.toString();
} else {
for (int i = numStr.length() - 1; i >= 0; i--) {
stringBuffer.append(numStr.charAt(i));
}
res = stringBuffer.toString();
}
return Integer.parseInt(res);
}
public static int reverse1(int x) {
int res = 0;
while (x != 0) {
// 每次取末尾數字
int tmp = x % 10;
// 判斷是否大於最大32位整數
if (res > 214748364 || (res == 214748364 && tmp > 7)) {
return 0;
}
// 判斷是否小於最小32位整數
if (res < -214748364 || (res == -214748364 && tmp < -8)) {
return 0;
}
res = res * 10 + tmp;
x = x / 10;
}
return res;
}
public static int reverse2(int x) {
int res = 0;
while (x != 0) {
int t = x % 10;
int newRes = res * 10 + t;
//如果數字溢出,直接返回0
if ((newRes - t) / 10 != res)
return 0;
res = newRes;
x = x / 10;
}
return res;
}
public static int reverse3(int x) {
long res = 0;
while (x != 0) {
res = res * 10 + x % 10;
x /= 10;
}
return (int) res == res ? (int) res : 0;
}
}
參考
- https://leetcode-cn.com/problems/reverse-integer/solution/tu-wen-xiang-jie-javadai-ma-de-2chong-shi-xian-fan/
- https://leetcode-cn.com/problems/reverse-integer/solution/tu-jie-7-zheng-shu-fan-zhuan-by-wang_ni_ma/
- https://blog.csdn.net/bingleihenshang/article/details/84838150