题目:String to Integer (atoi)

Implement atoi to convert a string to an integer.

分析:转换输入字符串为数字,原以为这是一道巨水题,单次提交通过肯定是没问题了,哪知......

题目要求:

  • 如果前面有空格开头,忽略掉开头的所有空格
  • 如果发现没意义的字符,忽略之,并结束转换。即123ttyw -> 123
  • 考虑负数额
  • 如果溢出,则返回相应的最大正数和最大负数。

考虑完这些问题后,我便傻哈拉地敲起了代码,敲完瞅了瞅觉得天衣无缝,我就是编译器一切尽在掌握的姿态有木有。

public class Solution {
    public int atoi(String str) {
        if (str.length() == 0) return 0;

        boolean positive = true;
        int len = str.length();

        int i = 0;
        // discard whitespace
        while (i < len && str.charAt(i) == ' ') {
            i++;
        }

        if (i < len && str.charAt(i) == '-') {
            positive = false;
            i++;
        }

        if (i < len && str.charAt(i) == '+') {
            i++;
        }

        int t = 0, num = 0;
        for (; i < len; i++) {
             t = str.charAt(i) - '0';
             if (t < 0 || t > 9) break;
             num = num*10 + t;
             if (num < 0) break;//already overflow..
        }

        // overflowed
        if (num < 0) {
            return positive ? Integer.MAX_VALUE : Integer.MIN_VALUE;
        }
        return positive ? num : -num;
    }
}

运行时出现了一个巨诡异的错误:

Input: " 10522545459"
Output: 1932610867
Expected: 2147483647

10522545459已经溢出了32位有符号整型的范围,但是怎么给我得出了1932610867这种诡异的结果。这就要涉及到补码的运算规则了(在《深入理解计算机系统》的第二章2.3.5节提到了补码乘法)。

当程序运行到num为1052254545时,扫到了9,于是便做

1052254545*10 + 9 => 10522545450 + 9

嗯,没错,即便溢出了,计算机也能给我们算出正确值的。坑就坑在计算完了之后,会对多出的位进行截断。

1052254545 * 10  = 10522545450 
0x3EB82151 * 0xA = 0x273314D2A (已经超出32位了,截掉多出的最高位2, 得到0x73314D2A

0x73314D2A对应的便是1932610858了,加上9,便得到了1932610867.

结论:切不可认为正数乘法溢出时一定得到负数啊。

所以判断的条件要这么改:

/**
 * MAX为最大整数,MIN为最小整数
 **/
if (MAX/10 >= num) {
    num = num*10 + t;
  } else{
    return positive ? MAX: MIN;
  }
if (num < 0) {
  return positive ? MAX: MIN;
}

如果MAX/10 >= num的话,那么num*10一定是小于等于MAX的,再加上t,分情况讨论:

  • 如果num*10 + t溢出,由于t < 10,根据补码的加法法则,num*10 + t一定是负数
  • 如果num*10 + t得到的是正数,则表明没有溢出,这一定是要求的结果
  • 如果要求的数字是负数

    • num*10 + t为正数时,取-(num*10 + t)即可
    • num*10 + t为负数时,表示已经溢出了正数范围。对于32位的补码而言,取值范围为-2^31 ~ 2^31 - 1,最小负数的绝对值仅比最大正数的绝对大1,故当超出正数范围时,我们直接取最小负数即可。

最终代码:

public class Solution {
    public int atoi(String str) {
        if (str.length() == 0) return 0;

        boolean positive = true;
        int len = str.length();
        int max = Integer.MAX_VALUE;
        int min = Integer.MIN_VALUE;

        int i = 0;
        // discard whitespace
        while (i < len && str.charAt(i) == ' ') {
            i++;
        }

        if (i < len && str.charAt(i) == '-') {
            positive = false;
            i++;
        }

        if (i < len && str.charAt(i) == '+') {
            i++;
        }

        int t = 0, num = 0;
        for (; i < len; i++) {
             t = str.charAt(i) - '0';
             if (t < 0 || t > 9) break;
             if (max/10 >= num) {
                 num = num*10 + t;
             } else{
                 return positive ? max : min;
             }
             if (num < 0) {
                return positive ? max : min;
             }

        }

        return positive ? num : -num;
    }
}

ssnau
1.5k 声望98 粉丝

负能量职业打码师


引用和评论

0 条评论