LeetCode 726. 原子的数量

726. 原子的数量

给你一个字符串化学式 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 总是有效的化学式

提示 1

To parse formula[i:], when we see a `'('`, we will parse recursively whatever is inside the brackets (up to the correct closing ending bracket) and add it to our count, multiplying by the following multiplicity if there is one. Otherwise, we should see an uppercase character: we will parse the rest of the letters to get the name, and add that (plus the multiplicity if there is one.)

解法1:栈 + 哈希表

对于括号序列相关的题目,通用的解法是使用递归或栈。本题中我们将使用栈解决。

从左到右遍历该化学式,并使用哈希表记录当前层遍历到的原子及其数量,因此初始时需将一个空的哈希表压入栈中。对于当前遍历的字符:

  • 如果是左括号,将一个空的哈希表压入栈中,进入下一层。
  • 如果不是括号,则读取一个原子名称,若后面还有数字,则读取一个数字,否则将该原子后面的数字视作 1。然后将原子及数字加入栈顶的哈希表中。
  • 如果是右括号,则说明遍历完了当前层,若括号右侧还有数字,则读取该数字 num,否则将该数字视作 1。然后将栈顶的哈希表弹出,将弹出的哈希表中的原子数量与 num 相乘,加到上一层的原子数量中。

遍历结束后,栈顶的哈希表即为化学式中的原子及其个数。遍历哈希表,取出所有「原子-个数」对加入数组中,对数组按照原子字典序排序,然后遍历数组,按题目要求拼接成答案。

算法实现步骤

  1. 初始化栈:将一个空哈希表压入栈中,用于记录第一层的原子计数。
  2. 遍历化学式
    • 对于每个字符 ch
      • 如果 ch 是 `'(``,压入一个新的空哈希表到栈中,表示新一层的开始。
      • 如果 ch 是大写字母,构建原子名称,并读取随后的数字(如果有),然后将其添加到栈顶哈希表中。
      • 如果 ch 是 `')``,读取随后的数字作为乘数,弹出栈顶的哈希表,将其内容乘以乘数,并合并到栈顶上一层的哈希表中。
  3. 处理结束:遍历完成后,栈顶的哈希表即为所需的原子计数。
  4. 构建结果字符串
    • 从栈顶哈希表中取出所有原子-数量对,按照原子名称的字典序排序。
    • 遍历排序后的原子-数量对,构建最终的字符串结果,如果数量大于1,则将数量附加到原子名称后面。

Java版:

class Solution {
    public String countOfAtoms(String formula) {
        int n = formula.length();
        int i = 0;
        Deque<Map<String, Integer>> stack = new LinkedList<>();
        stack.push(new HashMap<String, Integer>());
        while (i < n) {
            char ch = formula.charAt(i);
            if (ch == '(') {
                stack.push(new HashMap<String, Integer>());
                i++;
            } else if (ch == ')') {
                i++;
                int num = 0;
                if (i == n || !Character.isDigit(formula.charAt(i))) {
                    num = 1;
                } else {
                    while (i < n && Character.isDigit(formula.charAt(i))) {
                        num = num * 10 + (formula.charAt(i++) - '0');
                    }
                }
                Map<String, Integer> popMap = stack.pop();
                for (Map.Entry<String, Integer> entry : popMap.entrySet()) {
                    String atom = entry.getKey();
                    int v = entry.getValue();
                    stack.peek().merge(atom, v * num, Integer::sum);
                } 
            } else {
                StringBuilder atom = new StringBuilder();
                atom.append(ch);
                i++;
                while (i < n && Character.isLowerCase(formula.charAt(i))) {
                    atom.append(formula.charAt(i++));
                }
                int num = 0;
                if (i == n || !Character.isDigit(formula.charAt(i))) {
                    num = 1;
                } else {
                    while (i < n && Character.isDigit(formula.charAt(i))) {
                        num = num * 10 + (formula.charAt(i++) - '0');
                    }
                }
                stack.peek().merge(atom.toString(), num, Integer::sum);
            }
        }

        TreeMap<String, Integer> treeMap = new TreeMap<String, Integer>(stack.pop());
        StringBuilder ans = new StringBuilder();
        for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
            String atom = entry.getKey();
            int v = entry.getValue();
            ans.append(atom);
            if (v > 1) {
                ans.append(v);
            }
        }
        return ans.toString();
    }
}

Python3版:

from sortedcontainers import SortedDict
class Solution:
    def countOfAtoms(self, formula: str) -> str:
        n = len(formula)
        i = 0
        stack = []
        stack.append(defaultdict(int))
        while i < n:
            if formula[i] == '(':
                stack.append(defaultdict(int))
                i += 1
            elif formula[i] == ')':
                i += 1
                num = 0
                if i == n or not formula[i].isdigit():
                    num = 1
                else:
                    while i < n and formula[i].isdigit():
                        num = num * 10 + int(formula[i])
                        i += 1
                popMap = stack.pop()
                for atom, v in popMap.items():
                    stack[-1][atom] += v * num
            else:
                atom = formula[i]
                i += 1
                while i < n and formula[i].islower():
                    atom += formula[i]
                    i += 1
                num = 0
                if i == n or not formula[i].isdigit():
                    num = 1
                else:
                    while i < n and formula[i].isdigit():
                        num = num * 10 + int(formula[i])
                        i += 1
                stack[-1][atom] += num
        
        tree = SortedDict(stack.pop())
        ans = ''
        for atom, v in tree.items():
            ans += atom 
            if v > 1:
                ans += str(v) 
        return ans

复杂度分析

时间复杂度
  • O(n^2):最坏情况下,化学式中的括号可能导致栈达到 O(n) 层,每次处理右括号时,需要更新栈顶哈希表中所有原子的数量,这在最坏情况下可能需要 O(n) 的时间。因此,整个化学式的遍历可能达到 O(n^2) 的时间复杂度。
  • O(nlogn):在遍历结束后,需要对结果进行排序,这通常需要 O(nlogn) 的时间,其中 n 是不同原子的数量。

由于排序操作通常不会超过 nlogn,而遍历化学式的最坏情况时间复杂度为 n^2,因此总的时间复杂度被 n^2 所主导。

空间复杂度
  • O(n):空间复杂度由栈中存储的哈希表数量和它们的大小决定。栈的深度最多为 n,每个哈希表的大小不会超过化学式 formula 的长度。因此,总的空间复杂度为 O(n)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值