8.字符串转换整数(atoi)




8. 字符串转换整数 (atoi)
题意
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数
(类似 C/C++ 中的 atoi 函数)。
函数 myAtoi(string s) 的算法如下:
• 读入字符串并丢弃无用的前导空格
• 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。
确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
• 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分
将被忽略。
• 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果
没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
• 如果整数数超过 32 位有符号整数范围 [$ -2^{31} $, $ 2^{31} $ − 1] ,需要截断这个整
数,使其保持在这个范围内。具体来说,小于 $ -2^{31} $ 的整数应该被固定为 $
-2^{31} $ ,大于 $ 2^{31} $ − 1 的整数应该被固定为 $ 2^{31} $ − 1 。
• 返回最终结果。
示例
输入:s = "42"
输出:42
第 1 步:"42"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"42"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"42"(读入 "42",到达末尾)
^
解析得到整数 42 。由于 "42" 在 32 位有符号整数范围内,最终结果为 42 。
输入:s = " -42"
输出:-42
第 1 步:" -42"(读入前导空格,但忽视掉)
^
第 2 步:" -42"(读入 '-' 字符,所以结果应该是负数)
^
第 3 步:" -42"(读入 "42",到达末尾)
^
解析得到整数 -42 。由于 "-42" 在 32 位有符号整数范围内,最终结果为 -42 。
输入:s = "4193 with words"
输出:4193
第 1 步:"4193 with words"(当前没有读入字符,因为没有前导空格)
^
第 2 步:"4193 with words"(当前没有读入字符,因为这里不存在 '-' 或者 '+')
^
第 3 步:"4193 with words"(读入 "4193";由于下一个字符不是一个数字,所以读入停止)
^
解析得到整数 4193 。由于 "4193" 在 32 位有符号整数范围内,最终结果为 4193 。
难度
中
分析 1
其实这道题的描述对于 Java 党来说,实在是不够友好,在 Java 中,我们都喜欢叫“方
法”而不是函数,更没有 atoi 函数。
不过,读完这道题目,很容易联想到 Java 当中的 Integer.parseInt() 方法,这个方法就是
来完成数字字符串转整数的,对吧?
我之前在《二哥的 Java 进阶之路》上也带大家分析过该方法的源码,不过 parseInt 方法
仅处理了正负的情况,并没有处理空格的情况、字符串中包含字符的情况,不过我们可以
在此基础上进行扩展,来完成这道题目。
①、处理空格我们就用 String 的 trim() 方法来完成。
②、接着我们需要把字符串中数字部分找出来,这里我们可以通过 Character.isDigit()
方法来完成。我们在讲基本数据类型的包装器类型时曾讲过这个方法,不知道大家还记得
不?
唯一需要注意的是,我们需要考虑正负号的情况,所以我们需要在找到正负号后,再往后
找数字。
class Solution {
public int myAtoi(String str) {
if (str == null || str.length() == 0) {
return 0;
}
str = str.trim(); // 去除前导空格
if (str.length() == 0) return 0;
int sign = 1;
int index = 0;
// 检查正负号
if (str.charAt(0) == '+' || str.charAt(0) == '-') {
sign = str.charAt(0) == '-' ? -1 : 1;
index++;
}
// 提取数字
StringBuilder numberStr = new StringBuilder();
while (index < str.length() && Character.isDigit(str.charAt(index))) {
numberStr.append(str.charAt(index));
index++;
}
// 没有数字
if (numberStr.length() == 0) {
return 0;
}
// 尝试转换
try {
return Integer.parseInt(numberStr.toString()) * sign;
} catch (NumberFormatException e) {
return sign == -1 ? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
}
}
代码非常简单,先去掉空格,再判断正负,然后遍历字符串,找出数字的部分,最后尝试
通过 Integer.parseInt() 方法来转换,如果转换失败,就返回 Integer.MIN_VALUE 或者
Integer.MAX_VALUE。
比较遗憾的是,这样的代码效率不高。
008.%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%95%B4%E
6%95%B0-20240101191651.png
分析 2
效率低的原因在于,我们在找数字的时候,已经通过 Character.isDigit() 方法判断了字
符是否为数字,但是我们还是把它存到了 StringBuilder 中,最后再通过
Integer.parseInt() 方法来转换,这样做的话,就有点多此一举了。
我们可以直接在遍历的过程中,就把数字转换出来,这样就不需要再通过
Integer.parseInt() 方法来转换了。
class Solution {
public int myAtoi(String str) {
if (str == null || str.length() == 0) {
return 0;
}
str = str.trim(); // 去除前导空格
if (str.length() == 0) return 0;
int sign = 1;
int index = 0;
// 检查正负号
if (str.charAt(0) == '+' || str.charAt(0) == '-') {
sign = str.charAt(0) == '-' ? -1 : 1;
index++;
}
// 提取数字
int result = 0;
while (index < str.length() && Character.isDigit(str.charAt(index))) {
int digit = str.charAt(index) - '0';
if (result > (Integer.MAX_VALUE - digit) / 10) {
return sign == -1 ? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
result = result * 10 + digit;
index++;
}
return result * sign;
}
}
while 循环前面都很好懂,我来详细解释一下 while 循环中的代码:
while (index < str.length() && Character.isDigit(str.charAt(index))) {
// 将当前字符转换为对应的数字值
int digit = str.charAt(index) - '0';
// 溢出检查:如果当前的 result 大于最大整数减去当前数字除以 10,
// 则表示下一步操作(result * 10 + digit)会导致溢出
if (result > (Integer.MAX_VALUE - digit) / 10) {
// 根据符号返回最大或最小整数
return sign == -1 ? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
// 更新结果:将当前数字累加到结果中
result = result * 10 + digit;
// 移动到字符串的下一个字符
index++;
}
• 循环条件:index < str.length() && Character.isDigit(str.charAt(index)) 确保只
有在字符串没有结束且当前字符是数字时,循环才继续。
• 字符转数字:int digit = str.charAt(index) - '0'; 将字符型的数字转换为整型。在
ASCII 码表中,数字字符 ‘0’ 到 ‘9’ 是连续排列的,所以 str.charAt(index) - '0' 能够得
到相应的整数值。
• 溢出检查:if (result > (Integer.MAX_VALUE - digit) / 10) 检查是否即将发生溢
出。因为下一步操作是 result * 10 + digit,如果 result 已经大于
(Integer.MAX_VALUE - digit) / 10,则乘以 10 后加上 digit 一定会超出 int 的范
围。这里使用 (Integer.MAX_VALUE - digit) / 10 作为阈值来避免在乘法之后才检
测溢出,那时可能已经晚了。
• 更新结果:result = result * 10 + digit; 将当前数字字符的值累加到 result 中。这
里的 result * 10 是为了将之前的结果左移一位(即乘以 10),然后加上当前的数
字。
这个 while 循环就很精巧,其实就和 parseInt 方法的处理很接近了。
008.%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%95%B4%E
6%95%B0-20240101193056.png
OK,我们再来看一下运行效率。
008.%E5%AD%97%E7%AC%A6%E4%B8%B2%E8%BD%AC%E6%8D%A2%E6%95%B4%E
6%95%B0-20240101193123.png
果然提高了一大截。
总结
如果你之前认真学过 Java 基础的话,比如说去空格,比如说判断字符的正负号,比如说
判断字符是否为数字,比如说把一个字符转成数字。
这些知识点我在《二哥的 Java 进阶之路》上都有给大家讲过,比如说:
• 判断字符是否为数字
• 把一个字符转成数字
力扣链接:https://leetcode.cn/problems/string-to-integer-atoi/