一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情。
题目链接:385. 迷你语法分析器
题目描述
给定一个字符串 s 表示一个整数嵌套列表,实现一个解析它的语法分析器并返回解析的结果 NestedInteger 。
列表中的每个元素只可能是整数或整数嵌套列表
提示:
s由数字、方括号"[]"、负号'-'、逗号','组成- 用例保证
s是可解析的NestedInteger - 输入中的所有值的范围是
示例 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。
解题思路分析
本题的难点在于对接口的解读和题意的理解。
- 首先我们要明确的是:题目只是给了我们接口作为工具,我们需要利用这个接口工具去完成本题。
- 我们的目的是按照题目给我们的字符串
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]]]
- 这样题目就变得很简单了,我们只需要利用这三个接口函数方法和题目给定的字符串
s来构造一个NestedInteger结构即可。
具体实现
方法一:深度优先搜索
由题目得知我们需要构造的是一个嵌套列表,而这个 列表中的每个元素只可能是整数或整数嵌套列表 。所以 NestedInteger 是通过递归定义的,因此我们也可以用递归的方式来解析。
从左至右遍历字符串 s,
- 如果第一位是
'['字符,则表示待解析的NestedInteger是一个列表,我们需要递归构造这个列表,首先是构造函数初始化一个空的嵌套列表NestedInteger res = NestedInteger ();从'['后面的字符开始又是一个新的NestedInteger实例:- 如果是
'-'或者数字,表示待解析的NestedInteger只包含一个整数。我们可以从左至右解析这个整数,最后将这个整数作为NestedInteger结构添加到该层列表中res.add(NestedInteger(num));。 - 如果是
'['字符,表示在该列表中嵌套了一个新的列表,我们需要继续递归构造这个列表。 - 如果是
']'字符,表示这个列表已经解析完毕,可以返回NestedInteger实例。 - 否则只剩下
','字符,表示该层列表仍有其他元素,需要继续遍历。
- 如果是
- 如果第一位不是
'['字符,表示待解析的NestedInteger只包含一个整数,我们可以从左至右解析这个整数,并注意是否是负数,然后将这个整数作为NestedInteger结构返回即可。
复杂度分析
- 时间复杂度:,其中
n是s的长度。我们需要遍历s的每一位来解析。 - 空间复杂度:,其中
n是s的长度。深度优先搜索的深度最多为 ,需要 的栈空间。
方法二:栈
遇到这种类似括号匹配的问题,递归的思路也可以用栈来模拟。
从左至右遍历 s:
- 如果遇到
'[',则表示是一个新的 NestedInteger 实例,需要将其入栈。 - 如果遇到
']'或',',则表示是一个数字或者 NestedInteger 实例的结束,需要将其添加入栈顶的NestedInteger实例。最后需返回栈顶的实例。
复杂度分析
- 时间复杂度:,其中
n是s的长度。我们需要遍历s的每一位来解析。 - 空间复杂度:,其中
n是s的长度。栈的深度最多为 。
代码实现
- 深度优先搜索(易理解版)
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();
}
};
总结
本题的难点在于题目的理解,对接口的解读和题意得分析;其次是递归思想,题目对该结构的定义为嵌套列表,有递归的定义,我们很容易想到递归做法,递归做法分为两种,一种是函数递归,另一种就是栈递归,而函数递归其实是隐性的栈递归。这题类似于括号匹配的问题。
结束语
如果你眼下运气欠佳,请不要气馁。一朵花的凋零荒芜不了整个春天,一次挫折也击垮不了整个人生。过去的已无法撤回,但未来还可努力书写。要相信,人生路上的每一次磨砺都是未来的铺垫。