原子的数量

标签: 哈希表 字符串 排序

难度: Hard

给你一个字符串化学式 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}。

提示:

  • 1 <= formula.length <= 1000
  • formula 由英文字母、数字、'('')' 组成
  • formula 总是有效的化学式

Submission

运行时间: 29 ms

内存: 0.0 MB

class Solution:
    def countOfAtoms(self, formula: str) -> str:
        stack = []
        last_atom = ""
        coef = 1
        for i in range(len(formula)):
            s = formula[i]
            if s == "(":
                if last_atom != "":
                    stack.append((last_atom, 1))
                    last_atom = ""
                stack.append("(")
            elif s == ")":
                if last_atom != "":
                    stack.append((last_atom, 1))
                    last_atom = ""
            elif s.isnumeric():
                if formula[i-1].isnumeric():
                    continue
                j = i + 1
                while j < len(formula) and formula[j].isnumeric():
                    j += 1
                n = int(formula[i:j])
                if formula[i-1] != ")":
                    stack.append((last_atom, n))
                    last_atom = ""
                else:
                    coef = n
                    temp_stack = []
                    curr_pair = stack.pop()
                    while curr_pair != "(":
                        temp_stack.append((curr_pair[0], curr_pair[1] * coef))
                        curr_pair = stack.pop()
                    for k in range(len(temp_stack)-1, -1, -1):
                        stack.append(temp_stack[k])
            else:
                if s.islower():
                    last_atom += s
                    if i == len(formula)-1:
                        stack.append((last_atom, 1))
                else:
                    if last_atom == "":
                        last_atom = s
                        if i == len(formula)-1:
                            stack.append((last_atom, 1))
                    else:
                        stack.append((last_atom, 1))
                        last_atom = s
        counter = {}
        for pair in stack:
            if pair != "(":
                if pair[0] not in counter:
                    counter[pair[0]] = pair[1]
                else:
                    counter[pair[0]] += pair[1]
        res = []
        for atom, count in sorted(counter.items()):
            if count == 1:
                res.append(atom)
            else:
                res.append(atom+str(count))
        return "".join(res)

Explain

这个题解使用了栈的数据结构来解决问题。它从左到右遍历化学式字符串,对于每个字符进行分情况讨论: 1. 如果是左括号,将当前原子入栈,并清空当前原子。 2. 如果是右括号,将当前原子入栈,并清空当前原子。 3. 如果是数字,判断前一个字符是否也是数字,如果是则继续遍历;否则将当前原子和数字作为一个元组入栈,并清空当前原子。如果前一个字符是右括号,则将数字作为括号内原子的系数,将括号内的所有原子依次出栈并乘以系数后重新入栈。 4. 如果是小写字母,将其加入当前原子的名称中。 5. 如果是大写字母,将当前原子入栈,并将该大写字母作为新的当前原子。 遍历完成后,将栈中的所有原子及其数量存入哈希表中,最后按照字典序将哈希表中的原子拼接成字符串返回。

时间复杂度: O(nlogn)

空间复杂度: O(n)

class Solution:
    def countOfAtoms(self, formula: str) -> str:
        stack = []
        last_atom = ""
        coef = 1
        for i in range(len(formula)):
            s = formula[i]
            if s == "(":
                if last_atom != "":  # 将当前原子入栈
                    stack.append((last_atom, 1))
                    last_atom = ""
                stack.append("(")  # 将左括号入栈
            elif s == ")":
                if last_atom != "":  # 将当前原子入栈
                    stack.append((last_atom, 1))
                    last_atom = ""
            elif s.isnumeric():
                if formula[i-1].isnumeric():  # 如果前一个字符也是数字,则继续遍历
                    continue
                j = i + 1
                while j < len(formula) and formula[j].isnumeric():  # 找到完整的数字
                    j += 1
                n = int(formula[i:j])
                if formula[i-1] != ")": # 如果前一个字符不是右括号,将当前原子和数字作为一个元组入栈
                    stack.append((last_atom, n))
                    last_atom = ""
                else:  # 如果前一个字符是右括号,将数字作为括号内原子的系数
                    coef = n
                    temp_stack = []
                    curr_pair = stack.pop()
                    while curr_pair != "(":  # 将括号内的所有原子依次出栈并乘以系数
                        temp_stack.append((curr_pair[0], curr_pair[1] * coef))
                        curr_pair = stack.pop()
                    for k in range(len(temp_stack)-1, -1, -1):  # 将括号内的所有原子重新入栈
                        stack.append(temp_stack[k])
            else:
                if s.islower():  # 如果是小写字母,将其加入当前原子的名称中
                    last_atom += s
                    if i == len(formula)-1:  # 如果是最后一个字符,将当前原子入栈
                        stack.append((last_atom, 1))
                else:
                    if last_atom == "":  # 如果是大写字母,将其作为新的当前原子
                        last_atom = s
                        if i == len(formula)-1:  # 如果是最后一个字符,将当前原子入栈
                            stack.append((last_atom, 1))
                    else:  # 如果当前原子不为空,将其入栈,并将该大写字母作为新的当前原子
                        stack.append((last_atom, 1))
                        last_atom = s
        counter = {}
        for pair in stack:
            if pair != "(":  # 将栈中的所有原子及其数量存入哈希表中
                if pair[0] not in counter:
                    counter[pair[0]] = pair[1]
                else:
                    counter[pair[0]] += pair[1]
        res = []
        for atom, count in sorted(counter.items()):  # 按照字典序将哈希表中的原子拼接成字符串
            if count == 1:
                res.append(atom)
            else:
                res.append(atom+str(count))
        return "".join(res)

Explore

当遇到一个数字且其前一个字符是右括号时,这个数字表示括号内所有原子的倍数。算法首先会将这个数字存储为一个系数。然后,使用一个临时栈,逐一将括号内的原子(在'('之前)出栈,每个原子的数量乘以这个系数。出栈的过程会持续到遇到左括号为止,这标志着一个括号表达式的开始。处理完毕后,将处理结果(原子和新的数量)重新入栈。这样,括号内的所有原子都会以正确的数量被考虑在内。

在遍历化学式字符串时,每当遇到一个大写字母,它标志着一个新原子的开始。如果在此之前已经累积了一个原子的名称(即当前原子不为空),则需要先将这个原子及其数量(默认为1,除非紧随其后的是数字)入栈,然后再更新当前原子为新遇到的大写字母。这种处理确保了每个原子及其计数被正确记录并存储,以便于后续的数字处理或输出构建。如果不这样做,将导致原子计数错误或者原子名的覆盖,进而影响最终结果的正确性。

算法在遇到一个数字字符时,不会立即处理,而是检查后续字符是否也是数字,以此确保能够读取完整的数字(可能是多位数)。这通过初始化一个索引`j`为当前数字字符的下一位置开始,然后向后遍历,直到遇到非数字字符停止。这样就能正确解析出完整的数字。此逻辑能够准确处理包括连续数字在内的所有情况,确保不会因为数字的多位性而出错。

在字符串遍历完成后,算法遍历栈中的所有元素来构建一个哈希表,用于记录每个原子及其累积的数量。此过程中,算法检查栈中的每个元素,如果不是左括号(即是原子和数量的元组),则将原子名作为键,数量作为值添加到哈希表中。如果哈希表已存在该原子,其数量会累加。最后,根据哈希表中的数据按原子字典顺序生成最终的字符串。这一步骤确保了所有的原子计数被正确累加,并且按照要求的格式输出,是构建最终结果的关键。