Swift 字符串转整数算法题详解:myAtoi 实现与正则表达式对比
🧩 题目背景
LeetCode 上的经典算法题 8. String to Integer (atoi) 是一道考察字符串解析与边界处理的题目。这道题虽看似简单,但处理细节相当复杂。我们将使用 Swift 语言实现一个 myAtoi
函数,并分析其实现逻辑,同时引入正则表达式方式作对比,加深理解。
📋 题目要求
实现 myAtoi(_ s: String) -> Int
,将字符串转换为 32 位有符号整数,规则如下:
- 忽略前导空格。
- 处理正负号:若下一个字符是
+
或-
,记录符号。 - 解析数字:从当前位置开始,读取所有连续数字字符。
- 丢弃无效部分:遇到非数字字符即停止。
- 边界截断:如果数字超出 Int32 范围,需截断为
Int32.max
或Int32.min
。 - 返回最终结果。
🛠 原始实现(手动解析法)
func myAtoi(_ s: String) -> Int {
let chars = Array(s)
var i = 0
let n = chars.count
var sign = 1
var result = 0
// 1. 跳过前导空格
while i < n && chars[i] == " " {
i += 1
}
// 2. 处理符号
if i < n && (chars[i] == "-" || chars[i] == "+") {
sign = chars[i] == "-" ? -1 : 1
i += 1
}
// 3. 解析数字
// while i < n && ("0" <= chars[i] && chars[i] <= "9") { 或
while i < n, let digit = chars[i].wholeNumberValue {
// 溢出检测
if result > (Int(Int32.max) - digit) / 10 {
return sign == 1 ? Int(Int32.max) : Int(Int32.min)
}
result = result * 10 + digit
i += 1
}
return result * sign
}
🔍 分步注释说明
chars[i].wholeNumberValue
:安全判断字符是否为数字。- 溢出判断用
(Int32.max - digit) / 10
是防止越界的关键。 - 全过程不依赖标准库的数字转换函数,便于掌控每一步。
⚡ 使用正则表达式实现(更简洁)
func myAtoiWithRegex(_ s: String) -> Int {
let pattern = #"^\\s*([+-]?\\d+)"#
if let match = s.range(of: pattern, options: .regularExpression) {
let numberStr = String(s[match])
let num = Int64(numberStr.trimmingCharacters(in: .whitespaces)) ?? 0
if num < Int64(Int32.min) {
return Int(Int32.min)
} else if num > Int64(Int32.max) {
return Int(Int32.max)
} else {
return Int(num)
}
}
return 0
}
✅ 正则说明
^\\s* // 匹配前导空格
[+-]? // 匹配可选符号
\\d+ // 匹配一个或多个数字
正则版本简洁明了,适合快速提取符合格式的数值,但失去对每一步细节的掌控,不能做过细的错误处理和越界防护(比如字符级别的回退分析)。
🤔 小结:手写 vs 正则
方式 | 优点 | 缺点 |
---|---|---|
手动解析法 | 精准控制流程,逐步处理更安全 | 实现稍繁琐 |
正则表达式 | 代码更短,语义清晰 | 灵活性较低,越界控制需额外处理 |
🚀 拓展
- 可以拓展为解析浮点数(
myAtof
)或科学记数法(如1.2e3
) - 借助正则表达式快速实现原型,适合处理用户输入、日志、文本分析
📚 参考
- LeetCode 题解:https://leetcode.com/problems/string-to-integer-atoi/
- Swift 字符处理:https://developer.apple.com/documentation/swift/string
- 正则测试工具:https://regex101.com
👨🏫 下边给出浮点数&科学记数法代码自取
练习题 1:myAtof —— 字符串转浮点数(小数)
func myAtof(_ s: String) -> Double {
let pattern = #"^\s*([+-]?((\d+(\.\d*)?)|(\.\d+)))"#
let maxDouble = Double.greatestFiniteMagnitude
let minDouble = -Double.greatestFiniteMagnitude
if let match = s.range(of: pattern, options: .regularExpression) {
let numberStr = String(s[match]).trimmingCharacters(in: .whitespaces)
if let value = Double(numberStr) {
if value > maxDouble {
return maxDouble
} else if value < minDouble {
return minDouble
} else {
return value
}
}
}
return 0.0
}
/*
* • ^\s*:匹配开头和任意空格。
• [+-]?:可选的正负号。
• ((\d+(\.\d*)?)|(\.\d+)):两种小数形式:
• 123.456 或 123.(有整数部分,可有小数)
• .456(无整数部分,必须有小数)
*/
练习题 2:mySciToDouble —— 解析科学计数法字符串
func mySciToDouble(_ s: String) -> Double {
let maxDouble = Double.greatestFiniteMagnitude
let minDouble = -Double.greatestFiniteMagnitude
// 正则:提取科学计数法格式(e 或 E 可选正负号和数字)
let pattern = #"^\s*([+-]?((\d+(\.\d*)?)|(\.\d+))([eE][+-]?\d+)?)"#
if let match = s.range(of: pattern, options: .regularExpression) {
let numberStr = String(s[match]).trimmingCharacters(in: .whitespaces)
if let value = Double(numberStr) {
if value > maxDouble {
return maxDouble
} else if value < minDouble {
return minDouble
} else {
return value
}
}
}
return 0.0
}
/*
* • ^\s*:匹配开头和任意空格。
• [+-]?:可选的正负号。
• (\d+(\.\d*)?) 整数 + 小数点(如 123.45)
• ` (.\d+)`
• [eE][+-]?\d+ 可选科学记数法
*/