字符串转换整数 (atoi) 图解题解
跳空格、认符号、逐位拼数、钳范围——四步流水线,一遍扫完实现 atoi。
就像收银员读收据:先跳过开头的空白,认一下有没有正负号,然后逐位读数字(每多一位就把已读的数乘十再加进去),遇到第一个非数字字符立刻停手,最后把结果夹到整数上下限之间——四个阶段,顺序不乱、遇阻即停。
这道题到底在问什么
- s
- " -42"
- 输出
- -42
最优解:一步一步想明白
- 3核心就一句话:把字符串当流水线,分四个阶段往后扫。遇到不属于当前阶段的字符,就立刻停手。
- 4字符串 " -42",紫框=指针 i 正停的格,从第 0 格出发先认台上的格子:放的就是字符本身——三个空格、一个减号、再加 4 和 2。紫框是指针 i,现在停在第 0 格。
- 5while s[i]==' ' 时 i 一直加,跳过三个空格前三格都是空格,循环让 i 连跳三下。蓝色是跳掉的空格,紫框(i)正好停在第 3 格的减号上——第一个不是空格的字符。
- 6紫框停在第 3 格 '-',读到减号 → sign=-1,读完 i 前进到 4紫框正停在第 3 格,是个减号。把 sign 记成 -1,然后 i 前进到 4。符号只在这儿判一次,之后绝不再看。
- 7紫框停在第 4 格 '4':ans = 0 × 10 + 4 = 4,读完 i 前进到 5紫框停在第 4 格,是字符 4。老的 ans 先乘 10 腾出个位,再加上 4,得到 4。这是「逐位拼数」的标准动作。
- 8紫框停在第 5 格 '2':ans = 4 × 10 + 2 = 42,读完 i 前进到 6紫框停在第 5 格,是字符 2。ans 再乘 10 进位,加上 2,变成 42。每多一位,就「乘十再加」一次。
- 9假设第 6 格是字母 'a':紫框探到它,不是数字 → 不读、退出循环把「停」演出来:假设第 6 格是个灰色字母 a。紫框探到它,一判——不是数字,于是不读、不加、直接退出循环。ans 停在 42。
- 10ans = 42 × (-1) = -42;落在合法范围内,直接返回最后一步:ans 乘上 sign 得 -42(紫色是吃进答案的减号和 42)。它在 32 位范围内,不用夹,直接返回 -42。整道题收工。
- 11i:0→3(跳空格) · sign:1→-1(读减号) · ans:0→4→42(拼数)把全过程压成一帧:蓝色是被跳掉的三个空格,紫色是真正吃进答案的减号和 42。三个变量各管一摊,互不打架。
- 14一句话记住:空格 → 符号 → 数字 → 钳制,四步顺序走。任何阶段遇到不该出现的字符,就地停,绝不回头找。
- 19把这套四阶段扫描练熟,再上更复杂的字符串解析:LC65 有效数字(状态更多)、LC394 字符串解码(用栈管嵌套)、LC224 基本计算器(运算符加括号的状态机)。卡住就问问 AI 助教小欧,或去通关训练里实战一遍。
⚠️ 容易写错的地方
✗ 错:符号判完后又允许再读一个符号
✓ 对:符号只在数字前判一次,读完就 i++,之后只认数字
"+-12" 和 "-+12" 都没有合法数字,正确答案是 0
✗ 错:C++/Java 直接用 int 累加,不防溢出
✓ 对:中间用 long 存,或每次乘 10 前判断是否即将超界
"-91283472332" 远小于 INT_MIN,int 会先溢出再钳,得到的是错值
✗ 错:读到字母后跳过它,继续往后找数字
✓ 对:遇到第一个非数字字符就停,只取有效前缀
"4193 with words" 应得 4193,不是把后面零散的数字也算进来
完整代码(Python / C++ / Java)
Python
class Solution:
def myAtoi(self, s: str) -> int:
i, n = 0, len(s)
while i < n and s[i] == ' ': # 阶段1 跳空格
i += 1
sign = 1
if i < n and s[i] in '+-': # 阶段2 读符号
if s[i] == '-': sign = -1
i += 1
ans = 0
while i < n and s[i].isdigit(): # 阶段3 读数字
ans = ans * 10 + int(s[i])
i += 1
ans *= sign
return max(-2**31, min(2**31 - 1, ans)) # 阶段4 钳范围C++
class Solution {
public:
int myAtoi(string s) {
int i = 0, n = s.size();
while (i < n && s[i] == ' ') i++; // 阶段1
int sign = 1;
if (i < n && (s[i] == '+' || s[i] == '-')) { // 阶段2
if (s[i] == '-') sign = -1;
i++;
}
long ans = 0; // 用 long 防溢出
while (i < n && isdigit(s[i])) { // 阶段3
ans = ans * 10 + (s[i] - '0');
if (ans * sign <= (long)INT_MIN) return INT_MIN;
if (ans * sign >= (long)INT_MAX) return INT_MAX;
i++;
}
return (int)(ans * sign); // 阶段4
}
};Java
class Solution {
public int myAtoi(String s) {
int i = 0, n = s.length();
while (i < n && s.charAt(i) == ' ') i++; // 阶段1
int sign = 1;
if (i < n && (s.charAt(i) == '+' || s.charAt(i) == '-')) { // 阶段2
if (s.charAt(i) == '-') sign = -1;
i++;
}
long ans = 0; // 用 long 防溢出
while (i < n && Character.isDigit(s.charAt(i))) { // 阶段3
ans = ans * 10 + (s.charAt(i) - '0');
if (ans * sign <= Integer.MIN_VALUE) return Integer.MIN_VALUE;
if (ans * sign >= Integer.MAX_VALUE) return Integer.MAX_VALUE;
i++;
}
return (int)(ans * sign); // 阶段4
}
}套路模板 · 逐字符状态机骨架(挖空)
def parse(s):
i, n = 0, len(s)
# 阶段1:跳掉开头不要的字符(这里是空格)
while i < n and s[i] == ' ':
i += ___ # 填:i 怎么前进
# 阶段2:读一次性的标记(这里是正负号)
sign = 1
if i < n and s[i] in '+-':
sign = ___ # 填:'-' 时取 -1,否则 +1
i += 1
# 阶段3:连续读主体,遇到不合法就停
ans = 0
while i < n and ___: # 填:当前阶段的合法条件
ans = ans * 10 + int(s[i])
i += 1
# 阶段4:收尾(这里是乘符号 + 钳范围)
return ___ # 填:clamp(ans*sign)// 逐字符状态机三步走(套用到 atoi / 去空格 / 简单解析)
int i = 0, n = s.size(), sign = 1;
while (i < n && /* 跳过条件 */ s[i]==' ') i++; // 阶段1
if (i < n && (s[i]=='+' || s[i]=='-')) // 阶段2
sign = (s[i++]=='-') ? -1 : 1;
long ans = 0;
while (i < n && /* 合法条件 */ isdigit(s[i])) { // 阶段3
ans = ans * 10 + (s[i++] - '0');
if (ans*sign <= INT_MIN) return INT_MIN; // 边读边钳
if (ans*sign >= INT_MAX) return INT_MAX;
}
return (int)(ans * sign); // 阶段4// 逐字符状态机三步走(套用到 atoi / 去空格 / 简单解析)
int i = 0, n = s.length(), sign = 1;
while (i < n && s.charAt(i)==' ') i++; // 阶段1
if (i < n && (s.charAt(i)=='+' || s.charAt(i)=='-')) // 阶段2
sign = (s.charAt(i++)=='-') ? -1 : 1;
long ans = 0;
while (i < n && Character.isDigit(s.charAt(i))) { // 阶段3
ans = ans * 10 + (s.charAt(i++) - '0');
if (ans*sign <= Integer.MIN_VALUE) return Integer.MIN_VALUE;
if (ans*sign >= Integer.MAX_VALUE) return Integer.MAX_VALUE;
}
return (int)(ans * sign); // 阶段4复杂度
时间复杂度
O(n)
四个阶段加起来,每个字符最多被看一次,n 个字符就是 O(n)
空间复杂度
O(1)
全程只用 i、sign、ans 三个变量,不随输入长度增长
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 字符串转换整数 (atoi) 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
C++ 或 Java 里怎么防止读数字时整型溢出?+
用 long 存中间结果 ans,每加一位就判断 ans×sign 是否已经触到 INT_MIN 或 INT_MAX,触到就立刻提前返回边界值,不必等到最后再 clamp。Python 的 int 是大整数,天然不会溢出。
为什么读到第一个非数字字符就必须停,不能跳过它继续往后找数字?+
atoi 的语义是「转换有效整数前缀」。"12abc34" 的有效前缀只有 12,后面的 34 不连续、不算数。如果跳过非法字符继续读,就变成「提取串里所有数字」,那是另一道题了。
怎么保证正负号只会被读一次?+
符号检测只写成空格循环之后的那一个 if 分支,读完符号就 i++ 跳过。之后进入数字循环,循环条件只判 isdigit,而 '+'、'-' 都不是数字,自然再也回不到符号分支,逻辑上只能读一次。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。