LeetCode 8. 字符串转换整数 (atoi) 全解:字符处理、数字拼接与 32 位越界防护(LeetCode-Book 精选 88 题)
【免费下载链接】LeetCode-Book《剑指 Offer》《图解算法数据结构》《Krahets 笔面试精选 88 题》Python, Java, C++ 解题代码项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Book
本篇技术指南基于 LeetCode-Book 仓库中 《Krahets 笔面试精选 88 题》题解文档.md),系统讲解 LeetCode 第 8 题“字符串转换整数 (atoi)”的完整解法:如何分四类处理输入字符、如何用res = 10 × res + x进行数字拼接,以及在“环境只能存储 32 位有符号整数”约束下,如何用边界值bndry在拼接前完成越界预判。读完本文,你将掌握一套可复现的 Python / Java / C++ 实现,并能在手写 atoi 类问题时举一反三地处理空格、符号位与溢出边界。
一、题目核心:一个字符串里藏着哪四类字符
myAtoi(s)的输入是一个任意字符串,根据题意,从左到右扫描时只需要考虑以下四种字符,其余全部忽略:
- 首部空格:直接跳过(删除)即可,它们不参与数值转换。
- 符号位:只可能出现三种情况,即
'+'、'-'或“无符号”。需要新建一个变量保存符号位(sign),在返回结果前再乘上正负号。 - 非数字字符:遇到首个非数字字符时,应立即终止解析并返回当前已拼接的结果(不再向后扫描)。
- 数字字符:这是唯一需要“真正拼接”的字符,包含两个子步骤——字符转数字与数字拼接(详见下一节)。
这一分类贯穿整个解法:先处理空格与符号,再逐字符判断是否为数字,遇到非数字立即break/return,最后统一应用符号位。仓库源码中的注释与文档一致,例如 lc_8_string_to_integer_atoi_s1.py 按“删除首尾空格 → 判空 → 处理符号位 → 循环拼接 → 越界拦截”的顺序执行。
二、数字拼接公式:res = 10 × res + x
若从左向右遍历数字,设当前位字符为c,当前位数字为x,已拼接结果为res,则数字拼接公式为:
res = 10 × res + x x = ascii(c) - ascii('0')其中ascii(c) - ascii('0')即“该数字字符的 ASCII 码”与“0的 ASCII 码”相减,从而把字符'0'~'9'映射为整数0~9。在三种语言的实现中分别对应:
- Python:
res = 10 * res + ord(c) - ord('0')(ord取字符码点); - Java:
res = res * 10 + (c[j] - '0')(char参与算术运算时自动按码点计算); - C++:
res = res * 10 + (s[j] - '0')。
以输入" -42"为例:跳过空格后首字符为'-',置sign = -1,随后依次拼接'4'、'2',得到res = 42,最终返回sign * res = -42。这个“先拼绝对值、最后统一乘符号”的手法,使得正负数共用同一套拼接逻辑,代码更简洁。
三、32 位整数越界:为什么必须在拼接前拦截
题目要求返回值的范围是[-2^31, 2^31 - 1],即[-2147483648, 2147483647],并且明确指出“环境只能存储 32 位大小的有符号整数”。这意味着在拼接过程中,必须始终保持res处在 int 类型的取值范围内,否则在真正发生溢出(如 Python 大整数、C++ 有符号溢出)之前就要提前截断。
因此在每轮数字拼接之前,要先判断res在此轮拼接后是否超过2147483647,若超过则带上符号位直接返回最大值或最小值。设拼接边界:
bndry = 2147483647 // 10 = 214748364则存在以下两种越界情况:
res > bndry 情况一:执行拼接后 10 × res ≥ 2147483650,必然越界 res == bndry 且 x > 7 情况二:拼接后为 2147483648 或 2147483649,越界情况二需要解释:bndry × 10 + 7 = 2147483647恰好是 int 上限;若当前位数字x > 7,则拼接结果为2147483648或2147483649,均超出上限,必须按溢出处理。
在三种语言中,对应的判断写法分别是:
- Python:
if res > bndry or res == bndry and c > '7': return int_max if sign == 1 else int_min(用字符'7'与c比较,等价于与数字 7 比较); - Java:
if (res > bndry || res == bndry && c[j] > '7') return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; - C++:
if (res > bndry || res == bndry && s[j] > '7') return sign == 1 ? INT_MAX : INT_MIN;
其中bndry的取值也随语言略有差异,但数值等价:Python 中为2 ** 31 // 10,Java 中为Integer.MAX_VALUE / 10,C++ 中为INT_MAX / 10,结果都是214748364。仓库中 lc_8_string_to_integer_atoi_s2.java 与 lc_8_string_to_integer_atoi_s1.cpp 均以Integer.MAX_VALUE / 10、INT_MAX / 10初始化该边界。
四、方案一:先trim()/strip()再解析(空间 O(N))
第一种实现直接调用语言的去空格 API,先删除首尾空格再解析,逻辑直观、最贴近题面描述。
Python 实现
class Solution: def myAtoi(self, s: str) -> int: s = s.strip() # 删除首尾空格 if not s: return 0 # 字符串为空则直接返回 res, i, sign = 0, 1, 1 int_max, int_min, bndry = 2 ** 31 - 1, -2 ** 31, 2 ** 31 // 10 if s[0] == '-': sign = -1 # 保存负号 elif s[0] != '+': i = 0 # 若无符号位,则需从 i = 0 开始数字拼接 for c in s[i:]: if not '0' <= c <= '9' : break # 遇到非数字的字符则跳出 if res > bndry or res == bndry and c > '7': return int_max if sign == 1 else int_min # 数字越界处理 res = 10 * res + ord(c) - ord('0') # 数字拼接 return sign * res关键点在于:首字符为'-'时sign = -1且i = 1;首字符为'+'时保持i = 1(跳过符号位);首字符是数字或其它字符时i = 0(直接从首位开始拼接)。仓库文件 lc_8_string_to_integer_atoi_s1.py 与此实现逐行一致。
Java 实现
class Solution { public int myAtoi(String s) { char[] c = s.trim().toCharArray(); if (c.length == 0) return 0; int res = 0, bndry = Integer.MAX_VALUE / 10; int i = 1, sign = 1; if (c[0] == '-') sign = -1; else if (c[0] != '+') i = 0; for (int j = i; j < c.length; j++) { if (c[j] < '0' || c[j] > '9') break; if (res > bndry || res == bndry && c[j] > '7') return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; res = res * 10 + (c[j] - '0'); } return sign * res; } }方案一复杂度分析
- 时间复杂度 O(N):
N为字符串长度,线性遍历一次字符串,占用 O(N) 时间。 - 空间复杂度 O(N):删除首尾空格后需建立新字符串(Python 的
strip()、Java 的trim().toCharArray()都会产生新的字符串/字符数组对象),最差情况下占用 O(N) 额外空间。
五、方案二:手动跳过空格(空间 O(1))
若不使用trim() / strip()删除首部空格,而是改为“遍历跳过空格”的方式,则无需构造新字符串,可将空间复杂度降低至 O(1)。这也更贴近 C 语言手写atoi的经典风格。
Python 实现
class Solution: def myAtoi(self, s: str) -> int: res, i, sign, length = 0, 0, 1, len(s) int_max, int_min, bndry = 2 ** 31 - 1, -2 ** 31, 2 ** 31 // 10 if not s: return 0 # 空字符串,提前返回 while s[i] == ' ': i += 1 if i == length: return 0 # 字符串全为空格,提前返回 if s[i] == '-': sign = -1 if s[i] in '+-': i += 1 for j in range(i, length): if not '0' <= s[j] <= '9' : break if res > bndry or res == bndry and s[j] > '7': return int_max if sign == 1 else int_min res = 10 * res + ord(s[j]) - ord('0') return sign * res该实现与前者的差异集中在两点:一是用while s[i] == ' '循环手动推进下标跳过空格,并在i到达末尾时返回0(说明字符串全为空格);二是用if s[i] in '+-': i += 1一次性跳过符号位,而不再区分'+'与无符号的情况。仓库文件 lc_8_string_to_integer_atoi_s2.py 与此实现一致。
Java 实现
class Solution { public int myAtoi(String s) { int res = 0, bndry = Integer.MAX_VALUE / 10; int i = 0, sign = 1, length = s.length(); if(length == 0) return 0; while(s.charAt(i) == ' ') if(++i == length) return 0; if(s.charAt(i) == '-') sign = -1; if(s.charAt(i) == '-' || s.charAt(i) == '+') i++; for(int j = i; j < length; j++) { if(s.charAt(j) < '0' || s.charAt(j) > '9') break; if(res > bndry || res == bndry && s.charAt(j) > '7') return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; res = res * 10 + (s.charAt(j) - '0'); } return sign * res; } }C++ 实现
class Solution { public: int myAtoi(string s) { int res = 0, bndry = INT_MAX / 10; int i = 0, sign = 1, length = s.size(); if(length == 0) return 0; while(s[i] == ' ') if(++i == length) return 0; if(s[i] == '-') sign = -1; if(s[i] == '-' || s[i] == '+') i++; for(int j = i; j < length; j++) { if(s[j] < '0' || s[j] > '9') break; if(res > bndry || res == bndry && s[j] > '7') return sign == 1 ? INT_MAX : INT_MIN; res = res * 10 + (s[j] - '0'); } return sign * res; } };方案二复杂度分析
- 时间复杂度 O(N):仍为单次线性扫描,
N为字符串长度。 - 空间复杂度 O(1):全程只使用若干整数变量与下标,不创建新字符串,额外空间与输入规模无关。
六、仓库源码印证与本地运行方式
本仓库《Krahets 笔面试精选 88 题》(selected_coding_interview)为本题提供了与上述两种方案一一对应的三种语言实现,可直接对照阅读:
| 方案 | Python | Java | C++ |
|---|---|---|---|
| 方案一(strip/trim) | lc_8_string_to_integer_atoi_s1.py | lc_8_string_to_integer_atoi_s1.java | — |
| 方案二(手动跳空格) | lc_8_string_to_integer_atoi_s2.py | lc_8_string_to_integer_atoi_s2.java | lc_8_string_to_integer_atoi_s1.cpp |
从源码结构看,Python 与 Java 均给出 s1/s2 两版实现,C++ 则直接采用空间 O(1) 的手动跳空格版本,恰好印证了“优先使用 O(1) 空间写法”这一实践倾向。源码文件统一以from include import *(Python)、package lc_8_string_to_integer_atoi; import include.*;(Java)、#include "../include/include.hpp"(C++)引入仓库公共头文件,并附带main/ 驱动代码与测试用例占位,可在此基础上替换test_input验证各类输入:
# 以 Python 方案一为例 from include import * class Solution: def myAtoi(self, s: str) -> int: s = s.strip() if not s: return 0 res, i, sign = 0, 1, 1 int_max, int_min, bndry = 2 ** 31 - 1, -2 ** 31, 2 ** 31 // 10 if s[0] == '-': sign = -1 elif s[0] != '+': i = 0 for c in s[i:]: if not '0' <= c <= '9' : break if res > bndry or res == bndry and c > '7': return int_max if sign == 1 else int_min res = 10 * res + ord(c) - ord('0') return sign * res slt = Solution() for t in [" -42", "4193 with words", "words and 987", "-91283472332", "+1", " ", "2147483648"]: print(f"{t!r:>20} -> {slt.myAtoi(t)}")建议用以下用例自测,覆盖题面全部边界:
" -42"→-42(前导空格 + 负号);"4193 with words"→4193(数字后遇到非数字截断);"words and 987"→0(首个非空字符非数字,直接返回 0);"-91283472332"→-2147483648(负向越界,返回INT_MIN);"2147483648"→2147483647(正向越界,返回INT_MAX);" "→0(全空格,方案二在跳空格时提前返回)。
七、小结:这道题在考什么
字符串转换整数 (atoi) 是面试中典型的“模拟 + 边界”题,考点集中在:
- 字符分类能力:能否条理清晰地处理空格、符号、非数字与数字四类情况;
- 数字拼接功底:
res = 10 × res + x与 ASCII 码相减取数字; - 越界防御思维:在 32 位 int 约束下,用
bndry = INT_MAX / 10结合“res > bndry或res == bndry && 当前位 > 7”在拼接前拦截溢出,而不是等溢出发生后再补救; - 空间优化意识:同一逻辑可写出空间 O(N)(依赖
strip/trim)与空间 O(1)(手动跳空格)两种版本,理解二者的取舍。
本仓库对应的剑指 Offer 版本题解位于 sword_for_offer/docs/剑指 Offer 67. 把字符串转换成整数.md,可对照学习同一考点在《剑指 Offer》中的变体考查方式;更多“模拟 + 边界”类题目与配套三语言代码,可在 selected_coding_interview 目录下按题目编号继续检索。
【免费下载链接】LeetCode-Book《剑指 Offer》《图解算法数据结构》《Krahets 笔面试精选 88 题》Python, Java, C++ 解题代码项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Book
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考