原文链接:https://zhuanlan.zhihu.com/p/35160825?group_id=963485528066383872
题目描述
以字符串形式给出一个化学分子式,返回每个原子的计数。原子元素始终以大写字母开始,然后是零个或多个小写字母,代表名称。如果个数大于1,则可以跟随1个或多个代表该元素的个数。如果个数为1,则不会有数字。
例如,存在H2O和H2O2,但是不存在H1O2。两个式子连接在一起产生另一个式子。例如,H2O2He3Mg4也是一个式子。式子可以放在括号内,并可为整个带括号的式子添加个数。 例如,(H2O2)和(H2O2)3是合理的。
给定一个分子式,将所有元素的个数输出为以下形式的字符串:第一个原子名称(按排序顺序),后面是计数(如果该计数大于1);然后是第二个原子名称(按排序顺序 ),然后计数(如果该计数大于1),依此类推……
样例:
输入: “H2O”
输出: “H2O”
解释:元素计数为 {‘H’: 2, ‘O’: 1}.
输入: “Mg(OH)2”
输出: “H2MgO2”
解释:元素计数为 {‘H’: 2, ‘Mg’: 1, ‘O’: 2}.
输入: “K4(ON(SO3)2)2”
输出: “K4N2O14S4”
解释:元素计数为 {‘K’: 4, ‘N’: 2, ‘O’: 14, ‘S’: 4}.
注意:
1.所有的原子名称的非首字母都小写,首字母都大写。
2.分子式的长度将在[1,1000]的范围内。
3.分子式将只包含字母,数字和圆括号,并且是问题中的有效公式。
解题思路分析
这道题目稍许复杂,感觉像是在操作一段复杂的字符串,但细想之下其实整个过程并不繁琐。接下来给出三种方法,这三种方法本质相同,但实现方法上存在差异,第一种采用递归,而后两种采用栈式结构。
方法一:首先编写一个解析器Parse,这个解析器应当能统计出从当前字符串位置开始,直到遇到一个右括号“)”或读到字符串末尾,然后连带上右括号“)”后面可能跟上的数字,这可做为一段整体。用一个counter来记录这一段各个元素的个数。最后返回counter。
具体操作:
1.首先初始化counter
2.在解析器循环中:当遇到左括号“(”时,就递归调用Parse;并把Parse子程序中元素统计结果加入当前的counter当中。如果不遇到左括号,那么会发现一定是大写字母,也就是一个原子名称的开头,那么就统计这个元素的个数。
3.循环结束后,检查右括号后面是否跟有数字,如果有的话要对当前的counter进行相应倍乘。
4.最后返回counter。
Parse可以写成一个递归函数,然后用主函数做驱动函数。
方法二:和第一种方法类似,思想相同,但是写法上有差异。不采用递归函数,采用栈来进行操作。(其实函数的递归和栈本就具有极大的相似性)。
这时,先初始化counter栈,然后仍旧需要一层循环,直到读完所有字符。循环中:当遇到左括号“(”时一层新的counter入栈。遇到右括号“)”时栈顶counter弹出,并检查后面是否跟有数字,如果有的话对当前counter进行相应倍乘,并把结果累计到新露出的栈顶。遇到大写字母,就统计这个元素的个数,计入counter,类似方法一。
方法三:同方法二,但是不手动解析字符串,而是采用正则表达式(又称规则表达式Regular Expression,通常被用来检索、替换那些符合某个模式的文本),把字符串分成一个一个独立的块,这里采用的表达式为:”([A-Z][a-z])(\d)|(()|())(\d*)”。这里面的块分别表示:可能带有数字的元素如“Fe3”、“H”等;左括号“(”;可能带有数字的右括号“)”、“)3”等。然后在循环中对正则表达式中的每块进行各自对应的处理。
复杂度分析:以上无论哪种方法,理论的复杂度相同。但是这里推荐使用第三种方法,一者栈式结构比递归的写法更加简洁;二者正则表达式极大地简化匹配操作。时间复杂度为O(N^2),这里N为字符串的长度,遍历一遍字符串需要O(N)的时间,但是当遇到左括号时,会对子式的元素向外累加,这就有可能达到O(N)的时间,而总字符串长度为O(N),所以左括号的个数为O(N)(注意大O记号的含义是“不超过”,这里的意思是不超过线性界,而不是等于线性界)。故所花时间为O(N^2),这里给出一种时间比较逼近N^2的表达式:A(B(C(D(E(F3)4)5)6)7)。
注意:正则表达式的时间界不容易判断,这里正则表达式没有回溯,所以总的时间复杂度不会超过O(N^2),这与正则表达式的实现方法有关。
空间复杂度为O(N),这里无论是栈还是Map,空间复杂度不会超过O(N)。
参考程序
这里给出方法三,采用正则表达式的栈式写法
http://www.jiuzhang.com/solution/number-of-atoms/
面试官角度分析
本题是一道比较难的的题目,考察到了字符串的操作(实际上从形式语言与自动机的角度看,这是对于给定文法的一种特殊的解释执行),以及递归或者栈的灵活应用。如果能写出递归或者栈式结果,可以给出Hire;如果能了解并想到用正则表达式的方法,可以给出Strong Hire。
lintcode相关问题
http://www.lintcode.com/zh-cn/problem/url-parser/
http://www.lintcode.com/zh-cn/problem/regular-expression-matching/