编程之法:面试和算法心得(字符串转换成整数)
内容全部来自编程之法:面试和算法心得一书,实现是自己写的使用的是java
题目描述
输入一个由数字组成的字符串,把它转换成整数并输出。例如:输入字符串"123",输出整数123。
分析与解法
本题考查的实际上就是字符串转换成整数的问题,或者说是要你自行实现atoi函数。那如何实现把表示整数的字符串正确地转换成整数呢?以"123"作为例子:
- 当我们扫描到字符串的第一个字符'1'时,由于我们知道这是第一位,所以得到数字1。
- 当扫描到第二个数字'2'时,而之前我们知道前面有一个1,所以便在后面加上一个数字2,那前面的1相当于10,因此得到数字:1*10+2=12。
- 继续扫描到字符'3','3'的前面已经有了12,由于前面的12相当于120,加上后面扫描到的3,最终得到的数是:12*10+3=123。
因此,此题的基本思路便是:从左至右扫描字符串,把之前得到的数字乘以10,再加上当前字符表示的数字。
思路有了,你可能不假思索,写下如下代码:
public static int strToInt1(String s) { int n=0; for(int i=0;i<s.length();i++) { n = n*10+Character.getNumericValue(s.charAt(i)); } return n; }
显然,上述代码忽略了以下细节:
- 空指针输入:输入的是指针,在访问空指针时程序会崩溃,因此在使用指针之前需要先判断指针是否为空。(由于是java不存在空指针异常)
- 正负符号:整数不仅包含数字,还有可能是以'+'或'-'开头表示正负整数,因此如果第一个字符是'-'号,则要把得到的整数转换成负整数。
- 非法字符:输入的字符串中可能含有不是数字的字符。因此,每当碰到这些非法的字符,程序应停止转换。
- 整型溢出:输入的数字是以字符串的形式输入,因此输入一个很长的字符串将可能导致溢出。
首先我们处理问题2和3
思路很简单判断首位如果是+或者数字则为正数,如果是-则为负数。如果是其他字符则抛出异常。循环的过程中检查如果不为数字则抛出异常
代码如下
/* * 从左至右扫描字符串,把之前得到的数字乘以10,再加上当前字符表示的数字 * 无法处理超界限输入 */ public static int strToInt2(String s) throws Exception { int n=0; if(s.charAt(0)=='+') { for(int i=1;i<s.length();i++) { if((int)s.charAt(i)>=48&&(int)s.charAt(i)<=57) { n = n*10+Character.getNumericValue(s.charAt(i)); } else { throw new Exception("非首位必需为数字"); } } return n; } else if(s.charAt(0)=='-') { for(int i=1;i<s.length();i++) { if((int)s.charAt(i)>=48&&(int)s.charAt(i)<=57) { n = n*10+Character.getNumericValue(s.charAt(i)); } else { throw new Exception("非首位必需为数字"); } } return 0-n; } else if((int)s.charAt(0)>=48&&(int)s.charAt(0)<=57) { for(int i=0;i<s.length();i++) { if((int)s.charAt(i)>=48&&(int)s.charAt(i)<=57) { n = n*10+Character.getNumericValue(s.charAt(i)); } else { throw new Exception("非首位必需为数字"); } } return n; } else { throw new Exception("首位只能为+、-或数字"); } }
一般说来,当发生溢出时,取最大或最小的int值。即大于正整数能表示的范围时返回MAX_INT:2147483647;小于负整数能表示的范围时返回MIN_INT:-2147483648。
现在重点看看如何处理溢出
比较n和MAX_INT / 10的大小,即:
- 若n > MAX_INT / 10,那么说明最后一步转换时,n*10必定大于MAX_INT,所以在得知n > MAX_INT / 10时,当即返回MAX_INT。
- 若n == MAX_INT / 10时,那么比较最后一个数字c跟MAX_INT % 10的大小,即如果n == MAX_INT / 10且c > MAX_INT % 10,则照样返回MAX_INT。
对于上面第二种方式,先举两个例子说明下:
- 如果我们要转换的字符串是"2147483697",那么当我扫描到字符'9'时,判断出214748369 > MAX_INT / 10 = 2147483647 / 10 = 214748364(C语言里,整数相除自动取整,不留小数),则返回MAX_INT;
- 如果我们要转换的字符串是"2147483648",那么判断最后一个字符'8'所代表的数字8与MAX_INT % 10 = 7的大小,前者大,依然返回MAX_INT。
一直以来,我们努力的目的归根结底是为了更好的处理溢出,但上述第二种处理方式考虑到直接计算n 10 + c 可能会大于MAX_INT导致溢出,那么便两边同时除以10,只比较n和MAX_INT / 10的大小,从而巧妙的规避了计算n\10这一乘法步骤,转换成计算除法MAX_INT/10代替,不能不说此法颇妙。
代码如下
/* * 从左至右扫描字符串,把之前得到的数字乘以10,再加上当前字符表示的数字 */ public static int strToInt3(String s) throws Exception { int n=0; int maxInt = Integer.MAX_VALUE; int minInt = Integer.MIN_VALUE; if(s.charAt(0)=='+') { for(int i=1;i<s.length();i++) { if((int)s.charAt(i)>=48&&(int)s.charAt(i)<=57) { if(n<maxInt/10||(n==maxInt/10&&maxInt%10>Character.getNumericValue(s.charAt(i)))) { n = n*10+Character.getNumericValue(s.charAt(i)); } else { return maxInt; } } else { throw new Exception("非首位必需为数字"); } } return n; } else if(s.charAt(0)=='-') { for(int i=1;i<s.length();i++) { if((int)s.charAt(i)>=48&&(int)s.charAt(i)<=57) { if(n>minInt/10||(n==minInt/10&&(0-maxInt%10)>Character.getNumericValue(s.charAt(i)))) { n = n*10-Character.getNumericValue(s.charAt(i)); } else { return minInt; } } else { throw new Exception("非首位必需为数字"); } } return n; } else if((int)s.charAt(0)>=48&&(int)s.charAt(0)<=57) { for(int i=0;i<s.length();i++) { if((int)s.charAt(i)>=48&&(int)s.charAt(i)<=57) { if(n<maxInt/10||(n==maxInt/10&&maxInt%10>Character.getNumericValue(s.charAt(i)))) { n = n*10+Character.getNumericValue(s.charAt(i)); } else { return maxInt; } } else { throw new Exception("非首位必需为数字"); } } return n; } else { throw new Exception("首位只能为+、-或数字"); } }
其实代码思路上没有什么难度,主要问题在于各种异常的处理,这也是考验代码是否严谨。例如如果判断是否溢出用N*10这样则不能避免溢出。