故事背景
老马本名马占山,英文名字叫做Jack Ma。因为在公司工作年限比较长,技术特别好,又乐于助人,大家都亲切的叫他老马。大意是老马识途,别人解决不了的技术问题都喜欢问他。公司最近给他配置了一个新伙伴,刚刚毕业的王小二。王小二的大学很普通,因为刚刚毕业,经验比较少。但是非常的勤奋,很有干劲,不喜欢具体的业务需求,喜欢解决一些技术难点。老马最欣赏他的就是勤奋肯学,想法天马行空,往往能给出一些意想不到的答案。
Given a nested list of integers represented as a string, implement a parser to deserialize it.
Each element is either an integer, or a list -- whose elements may also be integers or other lists.
Note: You may assume that the string is well-formed:
-
String is non-empty.
-
String does not contain white spaces.
-
String contains only digits
0-9
,[
,-
,
,]
.
Example 1:
Given s = "324", You should return a NestedInteger object which contains a single integer 324.
Example 2:
Given s = "[123,[456,[789]]]", Return a NestedInteger object containing a nested list with 2 elements: 1. An integer containing value 123. 2. A nested list containing two elements: i. An integer containing value 456. ii. A nested list with one element: a. An integer containing value 789.正文
上班的第一天,老马决定给王小二布置一个任务:将指定的字符串转换成业务对象 NestedInteger。
老马担心王小二不会做,所以准备问问王小二的想法。
“小二,这道题你是咋想的啊?”
王小二摸了摸自己飘逸的头发(有几天没有洗了),说:“我觉得这道题挺简单的, 可以用eval直接将字符串转换成内存中的对象”。
老马点点头,王小二接着说:“主流语言都是支持直接将 JSON 字符串转换成 JSON Object 的。因为在 Web 开发时代,处理 JSON 是一个标准需求”。
老马点点头,“不错,的确可以这么做。但是这样做的话有些不足之处:第一,效率有点低,将逻辑处理交给底层的语言太过于复杂,几乎不可以优化;第二,安全性不好,如何 JSON 字符串中包括一些特殊的字符,比如 rm 等 ,有可能导致重大的安全事故。所以,这道题需要我们手动写代码实现转换的逻辑”, 老马说, “你还有其他的思路吗?”
王小二想了想,没有啥好的思路。不过他知道,既然要分析解析这个字符串,那么一定需要遍历字符串。其次,因为字符串中有几个特别的字符,肯定是需要特别处理的。他很老实的跟老马说了一下自己的想法:“我觉得字符串肯定需要遍历,然后遇到特殊的字符肯定要特殊处理。其他的就不知道了。”
老马说:“那你用伪代码描述一下你刚才的想法吧”。
王小二随手从旁边拿了一张白纸和笔,刷刷的写了几行伪代码。
遍历字符串s {
char = 当前的字符串
if(char == ‘[‘)
do...
else if char == ‘,'
do ...
else if char == ‘]'
do ...
else
do ...
}
王小二将字符分类成了四种: ] , [ 和数字。
王小二写完后,老马接着问他:“将字符分成四种类型挺不错的,接下来就应该考虑数据操作,你有什么好的想法吗?”。
王小二摇摇头,主流语言的字符串的API虽然很多,但是真没有合适处理这种情况的API。
老马看见王小二没有啥思路了,就继续说道: “字符串的处理是非常重要的,因为在一个IO密集的系统中,我们大部分时间都是在对数据进行增删改查。字符串有一个特点,就是方便传输。所以,HTTP协议就是用字符串传输的。而且,字符串也便于展示。但是字符串的操作比较麻烦,所以一般情况下,我们都需要借助其他的灵活的数据结构来处理字符串的问题”。
老马顿了顿说:“你觉得这个问题用什么数据结构比较好?”
王小二在仔细看了下题目,发现可以将 [A,[B, C]] 这种结构分成两个部分: A, [B,C]。当遇到 ‘]’ 符号的时候,就把 B 和 C 合成一个新的对象 D, 然后D 在入栈,新的数据变成了 [A,D],然后在按照上面的思路做一遍,不就可以求出答案了吗?王小二想通后,马上跟老马说:“栈”。
“对!”, 老马接着问:“你知道在处理栈的时候,有哪两点需要注意的吗?”
王小二皱着眉头,摇了摇头。虽然他大学学过数据机构,但是课上只讲了栈的定义和ADT。没有具体讲出栈和入栈的时间点(本来也讲不了,因为这跟具体的问题相关)。
老马见王小二不知道,接着说:“我觉得在用栈的时候,需要注意两点:第一,栈里面存的元素的类型;第二,栈的出栈和入栈的时间”。
老马知道王小二肯定没有思路,所以没有问,接着说:“我们仔细分析这个问题,可以发现有两种 NestedInteger : 容器类型的 NestedInteger 和 值类型的 NestedInteger 。结合我刚才说的注意事项,我们在 栈 中应该存放 容器类型的 NestedInteger ”, 老马看到王小二不断的点头,就准备提个问题考考他:“你知道为什么吗?”。
王小二不假思索的答道:“我觉得有两个原因:第一,如果第一个字符是[, 最后返回的类型一定是容器类型的 NestedInteger;第二,因为从 栈 中弹出的元素一定是要存入值类型的 NestedInteger 的,所以 栈 中使用 容器类型的NestedInteger 比较好”。
“恩”, 老马点点头:“元素类型确定后,我们只需要考虑入栈和出栈的时机了”,老马晃了晃刚才王小二写了伪码的纸说:“你刚才讲字符分成了四个类型,我们可以以这个四个类型做分析。”
“第一个类型是’[’ 。遇到 [ 的时候,应该是入栈。我们需要创建一个空的 容器 NestedInterger。”
“第二类型是’,’, 逗号说明前后两个数在同一个容器里,所以需要出栈,将数据存入弹出的NestedInteger 中,然后再入栈。等价于直接将数据放到栈的最后一个元素的list里。”
“第三个类型是’]’, 这个类型有点特殊,我们将数据存入弹出的 NestedInteger 后,不需要再入栈了,而是将该NestedInteger 存入 栈 顶的的 NestedInteger 中, 如果栈是空的,那么直接返回该 NestedInteger。”
“第四,数字不做处理”, 老马把手中的纸递给王小二说:“你根据这个思路,修改一下你的伪代码吧” 。
王小二接过纸后,仔细的回想了一遍老马刚才讲述的思路。飞快的写起来了
遍历字符串s {
char = 当前的字符串
if(char == ‘[‘)
stack.push(new NestedInterge())
else if char == ‘,'
stack.last.append(new NestedInterge(num))
else if char == ‘]'
item = stack.pop # 弹出最后一个容器
item.append(new NestedInterge(num)) # 存入数据
if stack.empty
return item
else
stack.last.append item
}
老马看着小二写完后,点了点头,准备问最后一个问题:“不错。还有一个问题,如何确定数字呢?”
王小二马上说道:“这个简单啊,我在遍历字符串的时候,记录下出现第一次出现特殊字符串的下一个位置和下一个特殊字符的上一个位置就行了”。
老马点点头说:“是的。看来你已经想清楚了,那么接下来写代码实现吧”。
在转身离开之前,老马抛出一个问题给王小二:“你可以想象一个扩展问题:如何用状态树解决这个问题?”。
王小二说:“好”。
马上全身心的投入到敲代码的伟大事业中去了。
… ...
晚上睡觉前,王小二想了想今天的收获:
-
使用其他灵活的数据结构来解决字符串的问题
-
栈的两个需要注意的点是:栈中的元素和出入栈的时间
最佳代码
二群 @yin 提交的 Java 版本,推荐理由是:代码简洁易懂,逻辑清晰,格式工整,有注释,兼顾了效率和可读性。
C++
Java
Python
Ruby