LeetCode 8. 字符串转换整数 (atoi) 解析
这个问题要求将字符串转换为 32 位有符号整数,需要处理前导空格、符号位、数字字符以及溢出情况。以下是详细解析:
方法思路
-
去除前导空格 :
从字符串开头跳过所有空格字符。
-
处理符号位 :
检查第一个非空格字符是否为
+
或-
,确定结果的符号。 -
转换数字字符 :
从符号位之后开始,连续读取数字字符(
0-9
)并转换为整数。遇到非数字字符时停止。 -
溢出处理 :
在转换过程中检查是否溢出 32 位有符号整数的范围(
[-2^31, 2^31-1]
)。
C++ 代码实现
cpp
#include <string>
#include <climits>
class Solution {
public:
int myAtoi(string s) {
int i = 0;
int n = s.length();
// 1. 跳过前导空格
while (i < n && s[i] == ' ') {
i++;
}
// 2. 处理符号位
int sign = 1;
if (i < n && (s[i] == '+' || s[i] == '-')) {
sign = (s[i] == '-') ? -1 : 1;
i++;
}
// 3. 转换数字并处理溢出
int res = 0;
while (i < n && isdigit(s[i])) {
int digit = s[i] - '0';
// 检查溢出:res * 10 + digit > INT_MAX
if (res > (INT_MAX - digit) / 10) {
return (sign == 1) ? INT_MAX : INT_MIN;
}
res = res * 10 + digit;
i++;
}
return sign * res;
}
};
代码解释
-
跳过前导空格 :
使用
while
循环将指针i
移动到第一个非空格字符位置。 -
处理符号位:
- 若遇到
+
,符号为正;若遇到-
,符号为负。 - 指针
i
后移一位,跳过符号字符。
- 若遇到
-
转换数字并处理溢出:
- 遍历后续字符,若为数字(
isdigit(s[i])
),则转换为整数。 - 溢出检查 :在累加前检查是否会导致溢出。若
res * 10 + digit > INT_MAX
,则根据符号返回INT_MAX
或INT_MIN
。
- 遍历后续字符,若为数字(
-
返回结果 :
将累加结果乘以符号位,得到最终转换结果。
复杂度分析
- 时间复杂度:O(n),其中 n 是字符串长度。只需遍历一次字符串。
- 空间复杂度:O(1),只需要常数级额外空间。
示例
情况1:正常转换
输入:s = " -42"
输出:-42
解释:
- 跳过前导空格后,遇到符号
-
,符号位设为-1
。 - 后续数字字符为
4
和2
,转换为整数42
,乘以符号位得到-42
。
情况2:溢出处理
输入:s = "2147483648"
(即 2^31
)
输出:2147483647
(即 INT_MAX
)
解释:
- 转换过程中检测到溢出(
214748364 * 10 + 8 > INT_MAX
),直接返回INT_MAX
。
情况3:包含非数字字符
输入:s = "4193 with words"
输出:4193
解释:
- 遇到非数字字符
' '
时停止转换,返回已转换的部分4193
。
关键点
-
溢出检查 :
使用
res > (INT_MAX - digit) / 10
代替直接比较res * 10 + digit > INT_MAX
,避免整数溢出。 -
提前终止 :
一旦遇到非数字字符,立即停止转换,避免无效处理。
-
符号位处理 :
符号位仅在第一个非空格字符处生效,其他位置的符号字符会被视为非数字字符而终止转换。
这种方法通过线性扫描和边界检查,高效且安全地完成字符串到整数的转换。
方法二:
cpp
#include <string>
#include <climits>
#include <unordered_map>
class Automaton {
private:
std::string state = "start";
std::unordered_map<std::string, std::vector<std::string>> table = {
{"start", {"start", "signed", "in_number", "end"}},
{"signed", {"end", "end", "in_number", "end"}},
{"in_number", {"end", "end", "in_number", "end"}},
{"end", {"end", "end", "end", "end"}}
};
// 获取字符类型对应的列索引
int get_col(char c) {
if (isspace(c)) return 0;
if (c == '+' || c == '-') return 1;
if (isdigit(c)) return 2;
return 3;
}
public:
int sign = 1;
long long ans = 0;
void get(char c) {
state = table[state][get_col(c)];
if (state == "signed") {
sign = (c == '+') ? 1 : -1;
} else if (state == "in_number") {
ans = ans * 10 + (c - '0');
// 处理溢出
ans = (sign == 1) ? std::min(ans, (long long)INT_MAX) :
std::min(ans, -(long long)INT_MIN);
}
}
};
class Solution {
public:
int myAtoi(string s) {
Automaton automaton;
for (char c : s) {
automaton.get(c);
if (automaton.state == "end") break;
}
return automaton.sign * automaton.ans;
}
};
自动机(有限状态机)原理详解
自动机(Finite State Machine, FSM)是一种抽象计算模型,它由一组状态和状态之间的转移规则组成。在字符串处理中,自动机特别适合处理需要根据输入字符序列按特定规则转换状态的问题。
一、基本概念
1. 核心组件
- 状态集合 :自动机所有可能的状态。例如,LeetCode 8 中的
{start, signed, in_number, end}
。 - 输入字符集 :所有可能的输入字符。例如,
{' ', '+', '-', '0'-'9', 其他字符}
。 - 转移函数 :定义状态之间的转换规则。例如,在状态
start
下遇到空格,转移到start
状态。 - 初始状态 :自动机的起始状态。例如,
start
。 - 终止状态 :自动机处理结束的状态。例如,
end
。
2. 工作流程
- 从初始状态开始。
- 按顺序读取输入字符,根据当前状态和输入字符,通过转移函数切换到下一状态。
- 重复步骤 2,直到处理完所有输入或进入终止状态。
二、LeetCode 8 的自动机设计
1. 状态定义
start
:初始状态,尚未处理任何有效字符。signed
:已处理符号位(+
或-
)。in_number
:正在处理数字字符。end
:遇到非法字符或处理完毕,终止处理。
2. 转移规则
用表格表示状态转移函数:
当前状态\输入字符 | 空格(' ') | 符号(+/-) | 数字(0-9) | 其他字符 |
---|---|---|---|---|
start |
start |
signed |
in_number |
end |
signed |
end |
end |
in_number |
end |
in_number |
end |
end |
in_number |
end |
end |
end |
end |
end |
end |
3. 状态转移图
start ──(空格)──→ start
├──(符号)──→ signed
└──(数字)──→ in_number
└──(其他)──→ end
signed ──(数字)──→ in_number
└──(其他)──→ end
in_number ──(数字)──→ in_number
└──(其他)──→ end
end ──(任何)──→ end
三、自动机的实现逻辑
1. 状态转移表的代码表示
cpp
std::unordered_map<std::string, std::vector<std::string>> table = {
{"start", {"start", "signed", "in_number", "end"}},
{"signed", {"end", "end", "in_number", "end"}},
{"in_number", {"end", "end", "in_number", "end"}},
{"end", {"end", "end", "end", "end"}}
};
- 行:当前状态。
- 列:输入字符类型(空格、符号、数字、其他)。
- 值:下一个状态。
2. 输入字符分类
cpp
int get_col(char c) {
if (isspace(c)) return 0; // 空格
if (c == '+' || c == '-') return 1; // 符号
if (isdigit(c)) return 2; // 数字
return 3; // 其他
}
3. 状态更新逻辑
cpp
void get(char c) {
state = table[state][get_col(c)]; // 根据当前状态和输入字符更新状态
if (state == "signed") {
sign = (c == '+') ? 1 : -1; // 记录符号位
} else if (state == "in_number") {
ans = ans * 10 + (c - '0'); // 累加数字
// 处理溢出
ans = (sign == 1) ? std::min(ans, (long long)INT_MAX) :
std::min(ans, -(long long)INT_MIN);
}
}
四、自动机处理示例
输入 :" -42"
- 初始状态 :
start
- 处理字符 :
' '
(空格):start → start
,保持初始状态。' '
(空格):start → start
。' '
(空格):start → start
。'-'
(符号):start → signed
,记录符号位-1
。'4'
(数字):signed → in_number
,累加数字4
。'2'
(数字):in_number → in_number
,累加数字4*10 + 2 = 42
。
- 结果 :
sign * ans = -1 * 42 = -42
五、自动机的优势
-
逻辑清晰 :
将复杂的条件判断转化为明确的状态转移表,避免嵌套的
if-else
语句,提高代码可读性。 -
可维护性强 :
当需求变化时(如增加新的状态或转移规则),只需修改转移表,无需大规模重构代码。
-
完整性 :
自动机强制覆盖所有可能的状态和输入组合,减少遗漏边界条件的风险。
-
扩展性 :
容易扩展到更复杂的字符串处理问题,如词法分析、语法解析等。
六、应用场景
自动机广泛应用于:
- 字符串匹配(如正则表达式引擎)
- 编译原理(词法分析、语法分析)
- 网络协议解析
- 游戏状态管理
- 人工智能中的行为决策
在处理具有明确状态转换规则的问题时,自动机是一种强大且优雅的解决方案。
方法三:
cpp
class Solution {
public:
int myAtoi(string s) {
stringstream ss(s);
int n;
ss >> n;
return n;
}
};