LeetCode 385 迷你语法分析器 Swift 题解:从字符串到嵌套数据结构的解析过程
在日常开发中,我们经常会遇到需要解析字符串的场景,比如配置文件、日志格式,甚至是网络传输中的 JSON 数据。这道题就类似于一个“小型 JSON 解析器”,给定一个描述嵌套列表的字符串,要求我们把它还原成嵌套的数据结构 NestedInteger。本文会从题目描述入手,逐步分析如何用栈的方式来解析字符串,并给出 Swift 的完整解法与运行示例。最后,我们也会结合实际场景聊聊这个题目背后的工程意义
摘要
在日常开发中,我们经常会遇到需要解析字符串的场景,比如配置文件、日志格式,甚至是网络传输中的 JSON 数据。这道题就类似于一个“小型 JSON 解析器”,给定一个描述嵌套列表的字符串,要求我们把它还原成嵌套的数据结构 NestedInteger
。
本文会从题目描述入手,逐步分析如何用栈的方式来解析字符串,并给出 Swift 的完整解法与运行示例。最后,我们也会结合实际场景聊聊这个题目背后的工程意义。
描述
题目的输入是一个字符串 s
,它可能是:
- 单个整数,比如
"324"
。 - 一个嵌套的整数列表,比如
"[123,[456,[789]]]"
。
目标是返回一个 NestedInteger
对象。这个类已经预定义好,支持以下操作:
isInteger()
:判断是否是单个整数。getInteger()
:获取整数值。getList()
:获取嵌套列表。add(_ elem: NestedInteger)
:往列表里添加一个元素。
换句话说,我们要写一个语法分析器,把字符串转换成 NestedInteger
。
题解答案
常见的解析方法有两种:
- 递归法:遇到
[
就递归解析子列表,遇到]
就返回。 - 栈法:遍历字符串,遇到
[
就新建一个列表对象压栈,遇到]
就出栈并加入上层。
这里我们采用 栈法,因为更直观,也更接近手动实现一个 JSON 解析器的过程。
题解代码分析
完整 Swift 实现如下:
import Foundation
// 模拟 LeetCode 提供的 NestedInteger 类
class NestedInteger {
private var integer: Int?
private var list: [NestedInteger] = []
init() {}
init(_ value: Int) {
self.integer = value
}
func isInteger() -> Bool {
return integer != nil
}
func getInteger() -> Int? {
return integer
}
func setInteger(_ value: Int) {
self.integer = value
}
func add(_ elem: NestedInteger) {
list.append(elem)
}
func getList() -> [NestedInteger] {
return list
}
// 方便打印调试
func toString() -> String {
if let value = integer {
return "\(value)"
} else {
return "[" + list.map { $0.toString() }.joined(separator: ",") + "]"
}
}
}
class Solution {
func deserialize(_ s: String) -> NestedInteger {
// 如果输入是单个整数,直接返回
if let value = Int(s) {
return NestedInteger(value)
}
var stack: [NestedInteger] = []
var numBuffer = ""
var negative = false
var current: NestedInteger? = nil
for char in s {
if char == "[" {
let ni = NestedInteger()
if let top = stack.last {
top.add(ni)
}
stack.append(ni)
} else if char == "]" {
if !numBuffer.isEmpty {
let num = (negative ? -1 : 1) * Int(numBuffer)!
stack.last?.add(NestedInteger(num))
numBuffer = ""
negative = false
}
current = stack.popLast()
} else if char == "," {
if !numBuffer.isEmpty {
let num = (negative ? -1 : 1) * Int(numBuffer)!
stack.last?.add(NestedInteger(num))
numBuffer = ""
negative = false
}
} else if char == "-" {
negative = true
} else if char.isNumber {
numBuffer.append(char)
}
}
return current!
}
}
代码解析
-
NestedInteger
类:我们自己实现一个简化版,方便测试。 -
deserialize()
方法:-
如果整个字符串是单个整数,直接返回。
-
用栈来保存当前的嵌套结构。
-
遍历字符串:
- 遇到
[
:新建一个列表对象,压入栈。 - 遇到数字或
-
:存入缓冲区,等到完整数字解析完成再入栈。 - 遇到
,
或]
:把缓冲区里的数字变成NestedInteger
对象,加入当前列表。
- 遇到
-
最后返回栈顶的对象。
-
这种方法跟手动解析 JSON 或 XML 很像,工程里也常见。
示例测试及结果
我们写一个小 demo 来测试:
let solution = Solution()
let ex1 = "324"
let result1 = solution.deserialize(ex1)
print("输入: \(ex1)")
print("输出: \(result1.toString())\n")
let ex2 = "[123,[456,[789]]]"
let result2 = solution.deserialize(ex2)
print("输入: \(ex2)")
print("输出: \(result2.toString())\n")
运行结果:
输入: 324
输出: 324
输入: [123,[456,[789]]]
输出: [123,[456,[789]]]
这跟题目要求完全一致。
时间复杂度
- 我们只遍历了一次字符串,每个字符都处理一次,所以 O(n)。
- 数字解析和栈操作都是常量时间。
空间复杂度
- 栈的深度最多等于嵌套层数。
- 所以空间复杂度是 O(n),主要用于存储结果对象和栈。
总结
这道题看起来是算法题,其实就是一个小型的 语法解析器。
它考察了两个核心点:
- 如何设计数据结构来表达嵌套关系。
- 如何用栈来解析字符串,避免递归层数过深。
在实际场景中,这个思路非常有用,比如:
- 写一个轻量级的配置解析器(像 JSON、YAML)。
- 解析游戏关卡脚本,描述嵌套的任务和事件。
- 从日志中提取嵌套的数据结构,方便分析。
所以,这不仅仅是一道刷题,更是把我们带入了“编译原理”的世界。
更多推荐
所有评论(0)