【C/C++】385. 迷你语法分析器

143 阅读8分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情


题目链接:385. 迷你语法分析器

题目描述

给定一个字符串 s 表示一个整数嵌套列表,实现一个解析它的语法分析器并返回解析的结果 NestedInteger

列表中的每个元素只可能是整数或整数嵌套列表

提示:

  • 1s.length51041 \leqslant s.length \leqslant 5 * 10^4
  • s 由数字、方括号 "[]"、负号 '-' 、逗号 ',' 组成
  • 用例保证 s 是可解析的 NestedInteger
  • 输入中的所有值的范围是 [106,106][-10^6, 10^6]

示例 1:

输入: s = "324",
输出: 324
解释: 你应该返回一个 NestedInteger 对象,其中只包含整数值 324。

示例 2:

输入:s = "[123,[456,[789]]]",
输出:[123,[456,[789]]]
解释:返回一个 NestedInteger 对象包含一个有两个元素的嵌套列表:
1. 一个 integer 包含值 123
2. 一个包含两个元素的嵌套列表:
    i.  一个 integer 包含值 456
    ii. 一个包含一个元素的嵌套列表
         a. 一个 integer 包含值 789

另外在代码区域的 接口注释

/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation
 * class NestedInteger {
 *   public:
 *     // Constructor initializes an empty nested list.
 *     NestedInteger();
 *
 *     // Constructor initializes a single integer.
 *     NestedInteger(int value);
 *
 *     // Return true if this NestedInteger holds a single integer, rather than a nested list.
 *     bool isInteger() const;
 *
 *     // Return the single integer that this NestedInteger holds, if it holds a single integer
 *     // The result is undefined if this NestedInteger holds a nested list
 *     int getInteger() const;
 *
 *     // Set this NestedInteger to hold a single integer.
 *     void setInteger(int value);
 *
 *     // Set this NestedInteger to hold a nested list and adds a nested integer to it.
 *     void add(const NestedInteger &ni);
 *
 *     // Return the nested list that this NestedInteger holds, if it holds a nested list
 *     // The result is undefined if this NestedInteger holds a single integer
 *     const vector<NestedInteger> &getList() const;
 * };
 */

整理题意

首先理解这个代码区域的 接口注释

  • This is the interface that allows for creating nested lists.
    • 这个接口允许创建嵌套列表。
  • You should not implement it, or speculate about its implementation
    • 你不应该实现它,或者猜测它的实现 通过这两句我们可以理解为: 题目给了我们类似接口一样的函数方法,这个接口中的函数方法是已经实现了的,我们可以直接调用。 然后下面给了接口 NestedInteger 类具体的函数方法名以及每个函数方法的功能(并没有给具体的实现代码)。

根据题目要求:我们需要利用题目给我们的函数方法接口,构造一个 NestedInteger (可以理解为一种存储结构),而构造的 NestedInteger 需要按照题目给我们的字符串 s 来构造,最后返回我们构造出来的这个 NestedInteger

以下是题目给我们的函数方法名对应的英文注释以及中文直译:

  • NestedInteger();:Constructor initializes an empty nested list.
    • 构造函数初始化一个空的嵌套列表。
  • NestedInteger(int value);:Constructor initializes a single integer.
    • 构造函数初始化一个整数。
  • bool isInteger() const;:Return true if this NestedInteger holds a single integer, rather than a nested list.
    • 如果这个NestedInteger包含一个整数,而不是一个嵌套的列表,则返回true。
  • int getInteger() const;:Return the single integer that this NestedInteger holds, if it holds a single integer. The result is undefined if this NestedInteger holds a nested list.
    • 返回这个NestedInteger保存的单个整数,如果它保存单个整数。如果这个NestedInteger持有一个嵌套的列表,则结果为undefined。
  • void setInteger(int value);:Set this NestedInteger to hold a single integer.
    • 设置这个NestedInteger为一个整数。
  • void add(const NestedInteger &ni);:Set this NestedInteger to hold a nested list and adds a nested integer to it.
    • 设置这个NestedInteger保存一个嵌套的列表,并向它添加一个嵌套的整数。
  • const vector<NestedInteger> &getList() const;:Return the nested list that this NestedInteger holds, if it holds a nested list. The result is undefined if this NestedInteger holds a single integer.
    • 返回这个NestedInteger持有的嵌套列表,如果它持有一个嵌套列表。如果NestedInteger保存单个整数,则结果为undefined。

解题思路分析

本题的难点在于对接口的解读和题意的理解。

  1. 首先我们要明确的是:题目只是给了我们接口作为工具,我们需要利用这个接口工具去完成本题。
  2. 我们的目的是按照题目给我们的字符串 s 来构造一个 NestedInteger 结构。而构造这个 NestedInteger 结构就需要用到题目给我们的接口,我们先看看哪些函数方法是我们用得上的,以及每个函数方法具体的效果。通过我们结合题意以及各个接口函数方法,最后我们筛选出能用上的接口方法有以下三个:
    • NestedInteger(); :构造函数初始化一个空的嵌套列表。通过本地测试我们可以得到这个函数构造结果为 NestedInteger ans = NestedInteger(); // ans = [] 这里的 ans 就是构造的结果,也就是这个构造函数的功能效果。
    • NestedInteger(int value); :构造函数初始化一个整数。通过本地测试我们可以得到这个函数构造结果为 NestedInteger ans = NestedInteger(123); // ans = 123 这里的 ans 就是构造的结果,也就是这个构造函数的功能效果。
    • void add(const NestedInteger &ni); :设置这个NestedInteger保存一个嵌套的列表,并向它添加一个嵌套的整数。下面通过题目示例2:s = "[123,[456,[789]]]" 来具体说明这三个接口函数方法的效果:
        NestedInteger ans = NestedInteger();    // ans = []
        ans.add(NestedInteger(789));            // ans = [789]
        NestedInteger temp = NestedInteger();   // temp = []
        temp.add(NestedInteger(456));           // temp = [456]
        temp.add(ans);                          // temp = [456, [789]]
        NestedInteger res = NestedInteger();    // res = []
        res.add(NestedInteger(123));            // res = [123]
        res.add(temp);                          // res = [123, [456, [789]]]
  1. 这样题目就变得很简单了,我们只需要利用这三个接口函数方法和题目给定的字符串 s 来构造一个 NestedInteger 结构即可。

具体实现

方法一:深度优先搜索

由题目得知我们需要构造的是一个嵌套列表,而这个 列表中的每个元素只可能是整数或整数嵌套列表 。所以 NestedInteger 是通过递归定义的,因此我们也可以用递归的方式来解析。

从左至右遍历字符串 s

  • 如果第一位是 '[' 字符,则表示待解析的 NestedInteger 是一个列表,我们需要递归构造这个列表,首先是构造函数初始化一个空的嵌套列表 NestedInteger res = NestedInteger ();'[' 后面的字符开始又是一个新的 NestedInteger 实例:
    • 如果是 '-' 或者数字,表示待解析的 NestedInteger 只包含一个整数。我们可以从左至右解析这个整数,最后将这个整数作为 NestedInteger 结构添加到该层列表中 res.add(NestedInteger(num));
    • 如果是 '[' 字符,表示在该列表中嵌套了一个新的列表,我们需要继续递归构造这个列表。
    • 如果是 ']' 字符,表示这个列表已经解析完毕,可以返回 NestedInteger 实例。
    • 否则只剩下 ',' 字符,表示该层列表仍有其他元素,需要继续遍历。
  • 如果第一位不是'[' 字符,表示待解析的 NestedInteger 只包含一个整数,我们可以从左至右解析这个整数,并注意是否是负数,然后将这个整数作为 NestedInteger 结构返回即可。

复杂度分析

  • 时间复杂度:O(n)O(n),其中 ns 的长度。我们需要遍历 s 的每一位来解析。
  • 空间复杂度:O(n)O(n),其中 ns 的长度。深度优先搜索的深度最多为 O(n)O(n),需要 O(n)O(n) 的栈空间。

方法二:栈

遇到这种类似括号匹配的问题,递归的思路也可以用栈来模拟。

从左至右遍历 s

  • 如果遇到 '[',则表示是一个新的 NestedInteger 实例,需要将其入栈。
  • 如果遇到 ']'',' ,则表示是一个数字或者 NestedInteger 实例的结束,需要将其添加入栈顶的 NestedInteger 实例。最后需返回栈顶的实例。

复杂度分析

  • 时间复杂度:O(n)O(n),其中 ns 的长度。我们需要遍历 s 的每一位来解析。
  • 空间复杂度:O(n)O(n),其中 ns 的长度。栈的深度最多为 O(n)O(n)

代码实现

  • 深度优先搜索(易理解版)
class Solution {
private:
    NestedInteger dfs(string s, int &i, int len){
        NestedInteger res = NestedInteger ();
        while(i < len){
            if(s[i] == '-' || (s[i] >= '0' && s[i] <= '9')){
                //是数字
                int num = 0;
                //负号处理
                int f = 1;
                if(s[i] == '-'){
                    f = -1;
                    i++;
                }
                //解析整数
                while(i < len && s[i] >= '0' && s[i] <= '9'){
                    num *= 10;
                    num += (s[i] - '0');
                    i++;
                }
                num *= f;
                res.add(NestedInteger(num));
            }
            else if(s[i] == '['){
                //是新的列表
                i++;
                res.add(dfs(s, i, len));
            }
            else if(s[i] == ']'){
                //结束当前列表
                i++;
                return res;
            }
            else{
                //s[i] == ',';
                i++;
            }
        }
        return res;
    }
public:
    NestedInteger deserialize(string s) {
        int i = 0;
        int len = s.length();
        //数字开头,注意还有可能是负号开头也是数字
        if(s[i] == '-' || (s[i] >= '0' && s[i] <= '9')){
            int num = 0;
            //负号处理
            int f = 1;
            if(s[i] == '-'){
                f = -1;
                i++;
            }
            //解析整数
            while(i < len && s[i] >= '0' && s[i] <= '9'){
                num *= 10;
                num += (s[i] - '0');
                i++;
            }
            //符号处理
            num *= f;
            return NestedInteger(num);
        }
        //否则就是'['列表左括号开头
        i++;
        return dfs(s, i, s.length());
    }
};

  • 深度优先搜索(优雅版)
class Solution {
public:
    int index = 0;
    NestedInteger deserialize(string s) {
        if (s[index] == '[') {
            index++;
            NestedInteger ni;
            while (s[index] != ']') {
                ni.add(deserialize(s));
                if (s[index] == ',') {
                    index++;
                }
            }
            index++;
            return ni;
        } else {
            bool negative = false;
            if (s[index] == '-') {
                negative = true;
                index++;
            }
            int num = 0;
            while (index < s.size() && isdigit(s[index])) {
                num = num * 10 + s[index] - '0';
                index++;
            }
            if (negative) {
                num *= -1;
            }
            return NestedInteger(num);
        }
    }
};

class Solution {
public:
    NestedInteger deserialize(string s) {
        if (s[0] != '[') {
            return NestedInteger(stoi(s));
        }
        stack<NestedInteger> st;
        int num = 0;
        bool negative = false;
        for (int i = 0; i < s.size(); i++) {
            char c = s[i];
            if (c == '-') {
                negative = true;
            } else if (isdigit(c)) {
                num = num * 10 + c - '0';
            } else if (c == '[') {
                st.emplace(NestedInteger());
            } else if (c == ',' || c == ']') {
                if (isdigit(s[i - 1])) {
                    if (negative) {
                        num *= -1;
                    }
                    st.top().add(NestedInteger(num));
                }
                num = 0;
                negative = false;
                if (c == ']' && st.size() > 1) {
                    NestedInteger ni = st.top();
                    st.pop();
                    st.top().add(ni);
                }
            }
        }
        return st.top();
    }
};

总结

本题的难点在于题目的理解,对接口的解读和题意得分析;其次是递归思想,题目对该结构的定义为嵌套列表,有递归的定义,我们很容易想到递归做法,递归做法分为两种,一种是函数递归,另一种就是栈递归,而函数递归其实是隐性的栈递归。这题类似于括号匹配的问题。


结束语

如果你眼下运气欠佳,请不要气馁。一朵花的凋零荒芜不了整个春天,一次挫折也击垮不了整个人生。过去的已无法撤回,但未来还可努力书写。要相信,人生路上的每一次磨砺都是未来的铺垫。