字符串转换整数(atoi)Java 题解:状态解析与边界截断
实现
myAtoi(String s),依次处理前导空格、正负号、连续数字和非法字符,并在结果超出 32 位有符号整数范围时返回对应边界。本文提供不依赖 64 位整数的 Java 实现、过程图解、完整测试及常见错误分析。

一、题目要求
字符串转整数需要严格按照以下顺序处理:
- 丢弃前导普通空格
' '; - 读取至多一个正负号,默认正数;
- 连续读取数字,遇到第一个非数字字符就停止;
- 没有读到任何数字时返回
0; - 小于
Integer.MIN_VALUE时返回最小值,大于Integer.MAX_VALUE时返回最大值。
32 位范围为:
text
[-2147483648, 2147483647]

二、几个容易误解的输入
text
"42" -> 42
" -042" -> -42
"1337c0d3" -> 1337
"0-1" -> 0
"words and 987" -> 0
0-1 不能解释成 -1,因为数字读取已经从 0 开始,遇到 - 后必须停止。words and 987 在第一个字符 w 处就停止,因此返回 0。
三、核心思路
使用下标 index 从左向右扫描字符串:
- 第一阶段跳过空格;
- 第二阶段读取一个可选符号;
- 第三阶段只要当前字符是数字,就将它追加到结果;
- 遇到非数字或字符串末尾时结束。
追加一位数字的公式是:
java
value = value * 10 + digit;
但不能计算完成后再判断溢出,因为 int 可能已经溢出。必须在乘以 10 之前预判。
四、不使用 long 的边界技巧
正数最大绝对值为 2147483647,负数允许的绝对值为 2147483648。后者无法用正 int 保存。
一个稳妥技巧是:始终用负数累计结果 。因为 int 的负数范围比正数多一个单位,-2147483648 可以被 int 直接保存。
java
result = result * 10 - digit;
正数输入使用限制 -2147483647,负数输入使用限制 -2147483648。在追加前检查:
java
if (result < limit / 10
|| (result == limit / 10 && -digit < limit % 10)) {
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
整个过程只使用 int。
五、可直接使用的 Java 答案
java
class Solution {
public int myAtoi(String s) {
int length = s.length();
int index = 0;
while (index < length && s.charAt(index) == ' ') {
index++;
}
int sign = 1;
if (index < length
&& (s.charAt(index) == '+' || s.charAt(index) == '-')) {
if (s.charAt(index) == '-') {
sign = -1;
}
index++;
}
int limit = sign == 1 ? -Integer.MAX_VALUE : Integer.MIN_VALUE;
int result = 0;
while (index < length) {
char current = s.charAt(index);
if (current < '0' || current > '9') {
break;
}
int digit = current - '0';
if (result < limit / 10
|| (result == limit / 10 && -digit < limit % 10)) {
return sign == 1
? Integer.MAX_VALUE
: Integer.MIN_VALUE;
}
result = result * 10 - digit;
index++;
}
return sign == 1 ? -result : result;
}
}
六、为什么采用负数累计
Java 的 int 范围并不对称:
text
最大值: 2147483647
最小值:-2147483648
如果使用正数累计,负数最小值的绝对值 2147483648 无法存入 int。使用负数累计后,正负两个边界都能在 int 中表达,最后只在正数情况下取反。
七、示例过程:" -042"
| 阶段 | 内容 | 状态 |
|---|---|---|
| 跳过空格 | 跳过 3 个空格 | index 指向 - |
| 读取符号 | 读取 - |
sign = -1 |
| 读取数字 | 读取 0、4、2 |
result = -42 |
| 返回 | 负数无需取反 | -42 |
前导零不需要单独删除。将 0 追加到当前结果不会改变数值,所以它会自然被忽略。
八、停止规则
只有紧跟在可选符号之后的连续数字才参与转换:
text
"1337c0d3" -> 读取 1337,遇到 c 停止
"+-12" -> 读取 + 后遇到 -,没有数字,返回 0
" + 12" -> 符号后遇到空格,没有数字,返回 0
注意:只丢弃最前面的普通空格。读取符号后,不会再次跳过空格。
九、复杂度分析
设字符串长度为 n:
- 时间复杂度:
O(n); - 额外空间复杂度:
O(1)。
每个字符最多访问一次,不创建与输入长度相关的额外容器。
十、完整可运行代码
java
public class StringToIntegerTest {
public static int myAtoi(String s) {
int index = 0;
int length = s.length();
while (index < length && s.charAt(index) == ' ') {
index++;
}
int sign = 1;
if (index < length
&& (s.charAt(index) == '+' || s.charAt(index) == '-')) {
sign = s.charAt(index) == '-' ? -1 : 1;
index++;
}
int limit = sign == 1 ? -Integer.MAX_VALUE : Integer.MIN_VALUE;
int result = 0;
while (index < length) {
char current = s.charAt(index);
if (current < '0' || current > '9') {
break;
}
int digit = current - '0';
if (result < limit / 10
|| (result == limit / 10 && -digit < limit % 10)) {
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
result = result * 10 - digit;
index++;
}
return sign == 1 ? -result : result;
}
private static void runTest(int number, String input, int expected) {
int actual = myAtoi(input);
System.out.println("测试 " + number + ": "
+ (actual == expected ? "PASS" : "FAIL")
+ " | input=\"" + input + "\""
+ " | actual=" + actual);
}
public static void main(String[] args) {
runTest(1, "42", 42);
runTest(2, " -042", -42);
runTest(3, "1337c0d3", 1337);
runTest(4, "0-1", 0);
runTest(5, "words and 987", 0);
runTest(6, "2147483648", Integer.MAX_VALUE);
runTest(7, "-2147483649", Integer.MIN_VALUE);
runTest(8, "+-12", 0);
runTest(9, "00000123", 123);
}
}
十一、测试结果
text
测试 1:PASS | "42" -> 42
测试 2:PASS | " -042" -> -42
测试 3:PASS | "1337c0d3" -> 1337
测试 4:PASS | "0-1" -> 0
测试 5:PASS | "words and 987" -> 0
测试 6:PASS | "2147483648" -> 2147483647
测试 7:PASS | "-2147483649" -> -2147483648
测试 8:PASS | "+-12" -> 0
测试 9:PASS | "00000123" -> 123
十二、常见错误
1. 使用 trim()
trim() 会创建新字符串,而且可能掩盖"只跳过前导普通空格"的读取规则。用下标扫描更直接。
2. 符号后再次跳过空格
"+ 12" 应返回 0,符号与数字之间不能插入空格。
3. 先计算再判断溢出
result * 10 + digit 可能先溢出。必须在追加数字前比较边界。
4. 使用 long 暂存
虽然写法简单,但负数累计可以完全在 int 范围内安全实现,更符合边界算法的训练目的。
5. 继续寻找后面的数字
遇到第一个非数字字符就必须停止,不能跳过它继续读取后面的数字。
十三、总结
字符串转换整数可以看成一个简单的顺序解析器:
- 跳过前导空格;
- 读取至多一个符号;
- 连续读取数字;
- 遇到其他字符立即停止;
- 在追加数字前预判边界;
- 越界时返回对应的 32 位极值。
使用负数累计可以在不依赖 64 位整数的情况下,统一处理 2147483647 和 -2147483648 两个不对称边界。
更多技术实践与案例,可在 📗【姚前述】中查阅。