哈希表题目:原子的数量

题目

标题和出处

标题:原子的数量

出处:726. 原子的数量

难度

8 级

题目描述

要求

给你一个表示化学式的字符串 formula \texttt{formula} formula,返回每种原子的数量。

原子总是以一个大写字母开始,跟随零个或任意个小写字母,表示原子的名字。

如果数量大于 1 \texttt{1} 1,原子后会跟着数字表示原子的数量。如果数量等于 1 \texttt{1} 1 则不会跟数字。

  • 例如, "H2O" \texttt{"H2O"} "H2O" "H2O2" \texttt{"H2O2"} "H2O2" 是可行的,但 "H1O2" \texttt{"H1O2"} "H1O2" 是不可行的。

两个化学式连在一起可以构成新的化学式。

  • 例如, "H2O2He3Mg4" \texttt{"H2O2He3Mg4"} "H2O2He3Mg4" 也是化学式。

由括号包含的化学式并佐以数字(可选择性添加)也是化学式。

  • 例如, "(H2O2)" \texttt{"(H2O2)"} "(H2O2)" "(H2O2)3" \texttt{"(H2O2)3"} "(H2O2)3" 是化学式。

返回所有原子的数量,格式为:第一个(按字典序)原子的名字,跟着它的数量(如果数量大于 1 \texttt{1} 1),然后是第二个原子的名字(按字典序),跟着它的数量(如果数量大于 1 \texttt{1} 1),以此类推。

题目保证输出的所有值都在 32 \texttt{32} 32 位整数范围内。

示例

示例 1:

输入: formula   =   "H2O" \texttt{formula = "H2O"} formula = "H2O"
输出: "H2O" \texttt{"H2O"} "H2O"
解释:原子的数量是 {‘H’:   2,   ‘O’:   1} \texttt{\{`H': 2, `O': 1\}} {‘H’: 2, ‘O’: 1}

示例 2:

输入: formula   =   "Mg(OH)2" \texttt{formula = "Mg(OH)2"} formula = "Mg(OH)2"
输出: "H2MgO2" \texttt{"H2MgO2"} "H2MgO2"
解释:原子的数量是 {‘H’:   2,   ‘Mg’:   1,   ‘O’:   2} \texttt{\{`H': 2, `Mg': 1, `O': 2\}} {‘H’: 2, ‘Mg’: 1, ‘O’: 2}

示例 3:

输入: formula   =   "K4(ON(SO3)2)2" \texttt{formula = "K4(ON(SO3)2)2"} formula = "K4(ON(SO3)2)2"
输出: "K4N2O14S4" \texttt{"K4N2O14S4"} "K4N2O14S4"
解释:原子的数量是 {‘K’:   4,   ‘N’:   2,   ‘O’:   14,   ‘S’:   4} \texttt{\{`K': 4, `N': 2, `O': 14, `S': 4\}} {‘K’: 4, ‘N’: 2, ‘O’: 14, ‘S’: 4}

数据范围

  • 1 ≤ formula.length ≤ 1000 \texttt{1} \le \texttt{formula.length} \le \texttt{1000} 1formula.length1000
  • formula \texttt{formula} formula 由英语字母、数字、 ‘(’ \texttt{`('} ‘(’ ‘)’ \texttt{`)'} ‘)’ 组成
  • formula \texttt{formula} formula 总是有效的化学式

解法

思路和算法

这道题需要使用到哈希表和栈这两种数据结构,哈希表用于记录每种原子的数量,栈用于处理化学式中的括号嵌套。栈内存储哈希表,哈希表用于记录当前层中每种原子的数量。将最外层(即整个化学式)记为第 1 1 1 层,从左到右遍历化学式,每遇到一个左括号则将层数加 1 1 1,每遇到一个右括号则将层数减 1 1 1,在任意时刻,栈内的哈希表个数等于当前遍历到的层数编号。

在遍历化学式之前,首先将一个空哈希表入栈,该哈希表用于记录整个化学式中每种原子的数量。从左到右遍历化学式的过程中,需要将化学式的每个成分分离,并对每个成分执行相应的操作。化学式中的成分包括以下四种:

  • 原子,为一个或多个连续的字母,第一个字母是大写,其余字母都是小写;
  • 原子或原子团的数量,为一个或多个连续的数字;
  • 左括号,表示原子团的开始;
  • 右括号,表示原子团的结束。

上述四种成分中,数量一定出现在一个原子或者一个原子团的后面,因此在处理完一个原子或者一个原子团之后即可处理数量,不需要单独处理数量。对于其余三种成分,需要在遍历过程中处理。

  • 如果遇到大写字母,则是一个原子的开始,执行如下操作。

    1. 继续遍历后面的字符,直到遍历到化学式末尾或者遇到的字符不是小写字母,此时得到原子名。

    2. 如果原子名的后面是数字,则继续遍历后面的字符,直到遍历到化学式末尾或者遇到的字符不是数字,此时得到原子的当前数量。如果原子名的后面不是数字,则当前数量为 1 1 1

    3. 从栈顶获得当前层的哈希表,在当前层的哈希表中将该原子的数量增加当前数量。

  • 如果遇到左括号,则是一个原子团的开始,进入下一层,因此将一个空的哈希表入栈。

  • 如果遇到右括号,则是一个原子团的结束,回到上一层,执行如下操作。

    1. 如果原子团的后面是数字,则继续遍历后面的字符,直到遍历到化学式末尾或者遇到的字符不是数字,此时得到原子团的数量。如果原子团的后面不是数字,则原子团的数量为 1 1 1

    2. 将原子团所在层的哈希表出栈,该哈希表称为原子团哈希表,该哈希表出栈之后位于栈顶的哈希表称为栈顶哈希表。

    3. 遍历原子团哈希表,对于原子团哈希表中的每个原子,其当前数量为哈希表中的数量乘以原子团的数量,在栈顶哈希表中将原子的数量增加当前数量。

遍历结束之后,栈内只剩下一个哈希表,将该哈希表出栈,该哈希表记录整个化学式中每种原子的数量。

由于返回所有原子的数量的格式要求是按照原子名的字典序排序,因此需要按照字典序依次遍历哈希表中的所有原子名,并将原子名和对应的原子数量拼接到结果中。排序的方法有多种,此处提供的解法使用优先队列存储哈希表中的所有原子名,优先队列的队首元素是字典序最小的原子名。

每次从优先队列中取出字典序最小的原子名,然后从哈希表中得到对应的原子数量,将原子名和原子数量拼接到结果中(如果原子数量是 1 1 1 则原子数量不拼接到结果中)。当优先队列为空时,所有的原子名和原子数量都加入到结果中。

代码

class Solution {
    public String countOfAtoms(String formula) {
        Deque<Map<String, Integer>> stack = new ArrayDeque<Map<String, Integer>>();
        stack.push(new HashMap<String, Integer>());
        int length = formula.length();
        int index = 0;
        while (index < length) {
            char c = formula.charAt(index);
            index++;
            if (Character.isUpperCase(c)) {
                StringBuffer temp = new StringBuffer();
                temp.append(c);
                while (index < length && Character.isLowerCase(formula.charAt(index))) {
                    temp.append(formula.charAt(index));
                    index++;
                }
                String atom = temp.toString();
                int count = 0;
                while (index < length && Character.isDigit(formula.charAt(index))) {
                    int digit = formula.charAt(index) - '0';
                    count = count * 10 + digit;
                    index++;
                }
                if (count == 0) {
                    count = 1;
                }
                Map<String, Integer> map = stack.peek();
                map.put(atom, map.getOrDefault(atom, 0) + count);
            } else if (c == '(') {
                stack.push(new HashMap<String, Integer>());
            } else {
                int multiple = 0;
                while (index < length && Character.isDigit(formula.charAt(index))) {
                    int digit = formula.charAt(index) - '0';
                    multiple = multiple * 10 + digit;
                    index++;
                }
                if (multiple == 0) {
                    multiple = 1;
                }
                Map<String, Integer> tempMap = stack.pop();
                Map<String, Integer> map = stack.peek();
                Set<Map.Entry<String, Integer>> entries = tempMap.entrySet();
                for (Map.Entry<String, Integer> entry : entries) {
                    String atom = entry.getKey();
                    int count = entry.getValue() * multiple;
                    map.put(atom, map.getOrDefault(atom, 0) + count);
                }
            }
        }
        Map<String, Integer> map = stack.pop();
        PriorityQueue<String> pq = new PriorityQueue<String>(map.keySet());
        StringBuffer output = new StringBuffer();
        while (!pq.isEmpty()) {
            String atom = pq.poll();
            int count = map.get(atom);
            output.append(atom);
            if (count > 1) {
                output.append(count);
            }
        }
        return output.toString();
    }
}

复杂度分析

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n n n 是字符串 formula \textit{formula} formula 的长度。
    栈内最多有 O ( n ) O(n) O(n) 层,每次出栈最多需要更新 O ( n ) O(n) O(n) 个原子的数量,因此计算每个原子的数量需要 O ( n 2 ) O(n^2) O(n2) 的时间。
    拼接结果时,优先队列中的元素个数是 O ( n ) O(n) O(n),共需要 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间拼接结果。
    因此总时间复杂度是 O ( n 2 + n log ⁡ n ) = O ( n 2 ) O(n^2 + n \log n) = O(n^2) O(n2+nlogn)=O(n2)

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是字符串 formula \textit{formula} formula 的长度。空间复杂度取决于栈内所有哈希表中的元素个数之和,以及优先队列中的元素个数,由于哈希表中的元素个数之和以及优先队列中的元素个数不会超过化学式的长度,因此空间复杂度是 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值