The "Number of Atoms" problem on LeetCode asks you to parse a chemical formula string and count the number of each type of atom present in the formula. The formula may contain nested parentheses, and numbers (subscripts) that indicate the number of times an atom or group of atoms appears.
For example, given the input formula = "K4(ON(SO3)2)2"
, you must return a string like "K4N2O14S4"
representing the count of each atom in lexicographical order.
At first, you might think to scan the string and count atoms as you see them. However, because of nested parentheses and multipliers, a simple scan won't work. For example, the atom count inside (SO3)2
must be multiplied by 2, and if that is nested deeper, it could be multiplied again by another number.
To solve this, we need a way to "remember" the multipliers for each group and apply them correctly, even when groups are nested. This naturally suggests using a stack to keep track of atom counts at each level of nesting. When we finish a group, we multiply all its counts by the group's multiplier and merge them with the outer level.
This is a classic example where a brute-force approach (just counting as you go) fails, and a structured approach using a stack is necessary for correctness and efficiency.
The core idea is to use a stack to handle nested parentheses and multipliers. Here’s the step-by-step process:
Let's trace the input "K4(ON(SO3)2)2"
step by step:
[{{}}]
[{{'K': 4}}]
[{{'K': 4}}, {{}}]
[{{'K': 4}}, {{'O': 1}}]
[{{'K': 4}}, {{'O': 1, 'N': 1}}]
[{{'K': 4}}, {{'O': 1, 'N': 1}}, {{}}]
[{{'K': 4}}, {{'O': 1, 'N': 1}}, {{'S': 1}}]
[{{'K': 4}}, {{'O': 1, 'N': 1}}, {{'S': 1, 'O': 3}}]
[{{'K': 4}}, {{'O': 1+6=7, 'N': 1, 'S': 2}}]
[{{'K': 4, 'O': 14, 'N': 2, 'S': 4}}]
"K4N2O14S4"
At each step, the stack structure ensures that multipliers are applied only to the correct group, even with deep or complex nesting.
The "Number of Atoms" problem teaches how to parse structured strings with nested groupings and multipliers. The key insight is to use a stack to manage nested contexts, ensuring multipliers are applied at the right scope. Using a hash map for atom counts allows efficient updates and merges. This approach is both efficient and elegant, handling complex nesting with clear, maintainable code.
from collections import defaultdict
class Solution:
def countOfAtoms(self, formula: str) -> str:
import re
stack = [defaultdict(int)]
i, n = 0, len(formula)
while i < n:
if formula[i] == '(':
stack.append(defaultdict(int))
i += 1
elif formula[i] == ')':
i += 1
start = i
while i < n and formula[i].isdigit():
i += 1
mult = int(formula[start:i] or '1')
popped = stack.pop()
for atom, count in popped.items():
stack[-1][atom] += count * mult
else:
start = i
i += 1
while i < n and formula[i].islower():
i += 1
atom = formula[start:i]
start_num = i
while i < n and formula[i].isdigit():
i += 1
count = int(formula[start_num:i] or '1')
stack[-1][atom] += count
result = ''
for atom in sorted(stack[-1]):
cnt = stack[-1][atom]
result += atom + (str(cnt) if cnt > 1 else '')
return result
#include <string>
#include <stack>
#include <unordered_map>
#include <map>
using namespace std;
class Solution {
public:
string countOfAtoms(string formula) {
stack<unordered_map<string, int>> stk;
stk.push(unordered_map<string, int>());
int n = formula.size(), i = 0;
while (i < n) {
if (formula[i] == '(') {
stk.push(unordered_map<string, int>());
++i;
} else if (formula[i] == ')') {
++i;
int start = i, mult = 1;
while (i < n && isdigit(formula[i])) ++i;
if (start < i) mult = stoi(formula.substr(start, i-start));
auto top = stk.top(); stk.pop();
for (auto &p : top) {
stk.top()[p.first] += p.second * mult;
}
} else {
int start = i++;
while (i < n && islower(formula[i])) ++i;
string atom = formula.substr(start, i-start);
int start_num = i, count = 1;
while (i < n && isdigit(formula[i])) ++i;
if (start_num < i) count = stoi(formula.substr(start_num, i-start_num));
stk.top()[atom] += count;
}
}
map<string, int> res;
for (auto &p : stk.top()) res[p.first] = p.second;
string ans;
for (auto &p : res) {
ans += p.first;
if (p.second > 1) ans += to_string(p.second);
}
return ans;
}
};
import java.util.*;
class Solution {
public String countOfAtoms(String formula) {
Deque<Map<String, Integer>> stack = new ArrayDeque<>();
stack.push(new HashMap<>());
int n = formula.length(), i = 0;
while (i < n) {
char c = formula.charAt(i);
if (c == '(') {
stack.push(new HashMap<>());
i++;
} else if (c == ')') {
i++;
int start = i, mult = 1;
while (i < n && Character.isDigit(formula.charAt(i))) i++;
if (start < i) mult = Integer.parseInt(formula.substring(start, i));
Map<String, Integer> popped = stack.pop();
for (String atom : popped.keySet()) {
stack.peek().put(atom, stack.peek().getOrDefault(atom, 0) + popped.get(atom) * mult);
}
} else {
int start = i++;
while (i < n && Character.isLowerCase(formula.charAt(i))) i++;
String atom = formula.substring(start, i);
int start_num = i, count = 1;
while (i < n && Character.isDigit(formula.charAt(i))) i++;
if (start_num < i) count = Integer.parseInt(formula.substring(start_num, i));
stack.peek().put(atom, stack.peek().getOrDefault(atom, 0) + count);
}
}
TreeMap<String, Integer> res = new TreeMap<>(stack.peek());
StringBuilder sb = new StringBuilder();
for (String atom : res.keySet()) {
sb.append(atom);
if (res.get(atom) > 1) sb.append(res.get(atom));
}
return sb.toString();
}
}
var countOfAtoms = function(formula) {
let stack = [new Map()];
let i = 0, n = formula.length;
while (i < n) {
if (formula[i] === '(') {
stack.push(new Map());
i++;
} else if (formula[i] === ')') {
i++;
let start = i;
while (i < n && /\d/.test(formula[i])) i++;
let mult = parseInt(formula.slice(start, i) || '1');
let popped = stack.pop();
for (let [atom, count] of popped.entries()) {
stack[stack.length-1].set(atom, (stack[stack.length-1].get(atom) || 0) + count * mult);
}
} else {
let start = i++;
while (i < n && formula[i] >= 'a' && formula[i] <= 'z') i++;
let atom = formula.slice(start, i);
let startNum = i;
while (i < n && /\d/.test(formula[i])) i++;
let count = parseInt(formula.slice(startNum, i) || '1');
stack[stack.length-1].set(atom, (stack[stack.length-1].get(atom) || 0) + count);
}
}
let atoms = Array.from(stack[0].entries()).sort((a, b) => a[0].localeCompare(b[0]));
let res = '';
for (let [atom, count] of atoms) {
res += atom + (count > 1 ? count : '');
}
return res;
};