题目描述

✅ 385. 迷你语法分析器

image-20260929094918412

image-20260929094918576

题意分析

将字符串解析为 NestedInteger:它既可以表示一个整数,也可以表示由若干 NestedInteger 组成的列表。列表可以为空,也可以继续嵌套,结果必须保留元素顺序与括号所表达的层级。

题目保证输入非空、语法合法,只含数字、负号、逗号和方括号,整数值处于给定范围内。因此这里只负责构造结构,不需要补充非法输入的校验或容错规则。纯整数也是合法的整体输入,并不一定包在列表中。

解法:栈模拟解析

核心思路

[!blue]

嵌套括号需要先完成内层,再回到外层继续添加元素,这正好符合栈的后进先出顺序。栈从底到顶保存所有已经读到左括号、尚未闭合的列表,栈顶就是当前整数或子列表应该加入的那一层。

遇到 [ 时创建空列表并入栈,开始处理它的内部。数字可能有多位并带负号,不能逐个字符创建整数;用 numberStart 记录整个整数片段的起点,-1 表示当前没有待提交的数字。遇到后续数字时继续向前扫描即可。

逗号或右括号意味着一个数字片段已经结束。若起点不是 -1,先解析 [numberStart,i),创建整数节点加入栈顶,再清空起点。这里用“是否存在片段”判断,整数值为零也会正常提交;空列表则没有片段,不会尝试解析空串。

如果当前字符是 ],提交末尾数字后再弹出已经完成的列表。栈中还有父层时,把这个完整子列表作为一个元素加入父层;栈已空时,闭合的就是最外层列表,直接返回。所有元素在输入中结束时依次加入所属列表,因此原顺序和嵌套关系都被保留。

纯整数没有括号或分隔符,也没有可以接收它的列表栈,所以入口发现首字符不是 [ 时直接解析整个字符串,构造整数结果。列表分支则由最外层右括号完成返回。

解题步骤

  1. 首字符不是左括号时,直接解析并返回一个整数节点。
  2. 否则创建空栈,令 numberStart = -1,逐字符扫描。
  3. 左括号建立新列表;数字或负号只在当前还没有起点时设置 numberStart。
  4. 遇到逗号或右括号,先提交可能存在的整数片段;若是右括号,再弹出完整列表,加入父层或返回最外层结果。

子列表闭合后的逗号只起分隔作用,此时数字起点已经清空,不会重复创建元素。Go 忽略整数转换错误,是因为题目保证数字片段合法且值在可表示范围内。

代码实现

class Solution {
    public NestedInteger deserialize(String s) {
        if (s.charAt(0) != '[') {
            return new NestedInteger(Integer.parseInt(s));
        }

        Deque<NestedInteger> stack = new ArrayDeque<>();
        int numberStart = -1;

        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);

            if (ch == '[') {
                stack.push(new NestedInteger());
            } else if (ch == '-' || Character.isDigit(ch)) {
                if (numberStart == -1) {
                    numberStart = i;
                }
            } else {
                // 遇到分隔符,先提交完整整数,再处理列表闭合。
                if (numberStart != -1) {
                    int value = Integer.parseInt(s.substring(numberStart, i));

                    stack.peek().add(new NestedInteger(value));
                    numberStart = -1;
                }

                if (ch == ']') {
                    NestedInteger completed = stack.pop();

                    // 外层列表完成后直接返回,不能继续访问父层。
                    if (stack.isEmpty()) {
                        return completed;
                    }

                    stack.peek().add(completed);
                }
            }
        }

        return new NestedInteger();
    }
}
import "strconv"

func deserialize(s string) *NestedInteger {
    if s[0] != '[' {
        value, _ := strconv.Atoi(s)
        result := &NestedInteger{}
        result.SetInteger(value)
        return result
    }

    stack := make([]*NestedInteger, 0)
    numberStart := -1
    for i := 0; i < len(s); i++ {
        ch := s[i]
        if ch == '[' {
            stack = append(stack, &NestedInteger{})
        } else if ch == '-' || ch >= '0' && ch <= '9' {
            if numberStart == -1 {
                numberStart = i
            }
        } else {
            // 遇到分隔符,先提交完整整数,再处理列表闭合。
            if numberStart != -1 {
                value, _ := strconv.Atoi(s[numberStart:i])
                integer := &NestedInteger{}
                integer.SetInteger(value)
                stack[len(stack)-1].Add(*integer)
                numberStart = -1
            }
            if ch == ']' {
                completed := stack[len(stack)-1]
                stack = stack[:len(stack)-1]
                // 外层列表完成后直接返回,不能继续访问父层。
                if len(stack) == 0 {
                    return completed
                }
                stack[len(stack)-1].Add(*completed)
            }
        }
    }
    return &NestedInteger{}
}

复杂度分析

  • 时间复杂度:$O(L)$,其中 $L$ 为输入长度。每个括号或分隔符处理一次,数字片段的解析总长度也不超过 $L$。
  • 空间复杂度:辅助栈为 $O(d+1)$,其中 $d$ 为嵌套深度;题目整数范围固定,单个数字片段的临时空间为常数。返回结构最多占 $O(L)$,不计入辅助栈空间。

关键点总结

[!green]

  • 先提交末尾数字,再关闭当前列表。
  • 负号和后续数字属于同一个整数片段。
  • 空列表没有待提交数字,直接完成当前栈层。

易错点总结

[!yellow]

  • 逐字符创建整数:多位数被拆成多个元素。
  • 闭合后不挂回父列表:子结构丢失。
  • 外层弹出后继续访问栈顶:栈已经为空。
  • 数字提交后不重置起点:后续分隔符会重复解析旧片段。

相似题目

题目 难度 关联与区别
341. 扁平化嵌套列表迭代器 中等 原题遍历已有嵌套整数结构,本题先从带括号文本构造这个结构。
394. 字符串解码 中等 同样解析带嵌套括号的文本,但原题括号外数字表示重复次数,本题数字是数据节点。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2022/36509448
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!