1. 问题描述:
给你一个字符串化学式 formula ,返回每种原子的数量 。原子总是以一个大写字母开始,接着跟随 0 个或任意个小写字母,表示原子的名字。如果数量大于 1,原子后会跟着数字表示原子的数量。如果数量等于 1 则不会跟数字。例如,"H2O" 和 "H2O2" 是可行的,但 "H1O2" 这个表达是不可行的。两个化学式连在一起可以构成新的化学式。例如 "H2O2He3Mg4" 也是化学式。由括号括起的化学式并佐以数字(可选择性添加)也是化学式。例如 "(H2O2)" 和 "(H2O2)3" 是化学式。返回所有原子的数量,格式为:第一个(按字典序)原子的名字,跟着它的数量(如果数量大于 1),然后是第二个原子的名字(按字典序),跟着它的数量(如果数量大于 1),以此类推。
示例 1:
输入:formula = "H2O"
输出:"H2O"
解释:原子的数量是 {'H': 2, 'O': 1}。
示例 2:
输入:formula = "Mg(OH)2"
输出:"H2MgO2"
解释:原子的数量是 {'H': 2, 'Mg': 1, 'O': 2}。
示例 3:
输入:formula = "K4(ON(SO3)2)2"
输出:"K4N2O14S4"
解释:原子的数量是 {'K': 4, 'N': 2, 'O': 14, 'S': 4}。
示例 4:
输入:formula = "Be32"
输出:"Be32"
提示:
1 <= formula.length <= 1000
formula 由英文字母、数字、'(' 和 ')' 组成
formula 总是有效的化学式
输出的所有值总是在 32-bit 整数范围内
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-atoms
2. 思路分析:
分析题目可以知道这是一道关于括号嵌套的问题,类似于力扣的385/394题。我们需要求解出最里层的括号的值...直到最外层括号的值,因为括号嵌套本身就是递归定义的所以我们可以使用递归来求解,当我们遇到左括号的时候递归求解括号里的值,然后返回到上一层。对于这道题来说我们可以先声明一个全局变量u来记录当前递归的位置,这样在往下递归返回到上一层(回溯)的时候递归的位置是最新的,在循环中进行递归。当遇到左括号的时候那么跳过左括号(u += 1),然后递归求解括号里的值,当前这一层递归调用结束之后跳过右括号,并且递归方法的返回值为一个字典(其实就是哈希表,用来记录递归调用结果的各个原子出现的数量,c++/java可以使用map),当前这一层的递归调用结束之后我们可以遍历递归调用的结果,将括号里面的原子数量乘以对应的次数累加到当前这一层字典对应的键中;当遇到右括号的时候直接break即可,如果之前存在递归调用那么会返回到上一层,没有则退出当前循环,当遇到字母的时候需要判断后面的字母是否是小写字母,通过循环来截取对应的原子,然后判断后面是否存在数字如果存在数字那么需要截取对应的数字,表示原子需要重复的次数,然后将对应的原子重复对应的次数累加到当前这一层的字典的键中,主要还是理解递归调用的思想。
3. 代码如下:
import collections
class Solution:
# 声明一个全局变量来跟踪当前递归调用的位置这样才能够保证回溯的时候递归的位置才是最新的, 如果是往下传递的方法那么在回溯的时候递归的位置不是最新的
u = 0
def dfs(self, s: str):
# 每一次往下递归调用的时候都会声明一个新的字典, 所以每一层的字典都是不一样的, 当前这一层的字典记录当前这一层原子的值和递归调用处理的结果的值
dic = collections.defaultdict(int)
while self.u < len(s):
# 遇到左括号需要递归处理
if s[self.u] == "(":
# 跳过左括号
self.u += 1
t = self.dfs(s)
# 跳过右括号
self.u += 1
k = self.u
count = 1
# 截取当前括号的原子需要重复的次数
while k < len(s) and "0" <= s[k] <= "9": k += 1
if k > self.u:
count = int(s[self.u: k])
self.u = k
for k, v in t.items():
dic[k] += v * count
# break可以退出循环, 退出循环之后如果之前存在递归调用那么会返回到上一层
elif s[self.u] == ")":
break
else:
# k为下一个位置
k = self.u + 1
# 判断是否是小写字母, 小写字母与之前的第一个大写字母属于同一个原子
while k < len(s) and "a" <= s[k] <= "z": k += 1
# 当前位置对应的原子
key = s[self.u: k]
# 更新全局变量
self.u = k
k = self.u
count = 1
# 截取原子需要重复的次数
while k < len(s) and "0" <= s[k] <= "9": k += 1
if k > self.u: count = int(s[self.u: k])
# 更新全局变量
self.u = k
dic[key] += count
return dic
# 括号嵌套
def countOfAtoms(self, s: str) -> str:
dic = self.dfs(s)
# 按照字符串的字典序大小排序, w表示字典的键值对对应的元组
dic = sorted(dic.items(), key=lambda w: (w[0], w[1]))
res = ""
for x in dic:
if x[1] > 1: res += x[0] + str(x[1])
else: res += x[0]
return res