携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第38天,原子的数量[括号匹配问题] - 掘金 (juejin.cn)
前言
括号匹配问题,采用栈/递归来解决,定义全局变量idx,访问完整个字符串。掌握了这个基本功,才能在此基础上做一些复杂的事。
一、原子的数量
二、括号匹配
1、递归实现
// 原子的数量
public class CountOfAtoms {
/*
统计化学式中原子出现的个数,每个原子;接数字,就是它的原子数,若不接数字,则表示1个。
用Map来记录每个原子的个数,主要是括号的处理,三种括号,1-嵌套括号(());2-组合括号()();3-组合&嵌套(()());
括号问题得交给递归(回溯融合每层hashMap)/栈处理,每层括号得到一个hashMap,得到右侧倍数时,
*/
public String countOfAtoms(String formula) {
// 递归的方式得到最终的原子个数。
Map<String, Integer> fx = dfs(formula);
// 用treeMap进行排序。
TreeMap<String, Integer> tm = new TreeMap<>(fx);
// 将map结构转换为string
StringBuilder sb = new StringBuilder();
while (!tm.isEmpty()) {
Map.Entry<String, Integer> entry = tm.pollFirstEntry();
sb.append(entry.getKey());
if (entry.getValue() != 1) sb.append(entry.getValue());
}
return sb.toString();
}
int idx = 0;// 访问字符串下标。
private Map<String, Integer> dfs(String formula) {
// 该小括号里呈现的原子以及原子数量。
Map<String, Integer> fx = new HashMap<>();
while (idx < formula.length() && formula.charAt(idx) != ')') {
// 碰到左括号,递归下去获取该括号内的Map
if (formula.charAt(idx) == '(') {
idx++;
// 融合到fx中。
mapFusion(dfs(formula), fx, getRightNum(formula));
}
// 碰到普通的数字,
else {
String key = getAtomName(formula);
Integer value = getRightNum(formula);
fx.put(key, fx.getOrDefault(key, 0) + value);
}
}
++idx;// 碰到右括号了。
return fx;
}
// 将一个map的n倍融合到另一个map中。
public void mapFusion(Map<String, Integer> oldMap, Map<String, Integer> newMap, int n) {
Set<Map.Entry<String, Integer>> entries = oldMap.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
Integer value = entry.getValue() * n;
newMap.put(key, newMap.getOrDefault(key, 0) + value);
}
}
// 获得整个原子名称。
public String getAtomName(String formula) {
StringBuilder sb = new StringBuilder();
sb.append(formula.charAt(idx));
while (++idx < formula.length() && Character.isLowerCase(formula.charAt(idx)))
sb.append(formula.charAt(idx));
return sb.toString();
}
// 获取右侧原子的个数。
public int getRightNum(String formula) {
// 后面不带数字 || 后面无字符,默认为1.
if (idx == formula.length() || !Character.isDigit(formula.charAt(idx))) return 1;
// 循环判定 + 10倍扩数的方式。
int num = 0;
while (idx < formula.length() && Character.isDigit(formula.charAt(idx))) {
num = num * 10 + formula.charAt(idx) - '0';
++idx;
}
return num;
}
}
2、栈模拟(push需要的信息)
// 用栈来实现。
class CountOfAtoms2 {
/*
统计化学式中原子出现的个数,每个原子;接数字,就是它的原子数,若不接数字,则表示1个。
用Map来记录每个原子的个数,主要是括号的处理,三种括号,1-嵌套括号(());2-组合括号()();3-组合&嵌套(()());
括号问题得交给递归(回溯融合每层hashMap)/栈处理,每层括号得到一个hashMap,得到右侧倍数时,
*/
public String countOfAtoms(String formula) {
// 递归的方式得到最终的原子个数。
Map<String, Integer> fx = count(formula);
// 用treeMap进行排序。
TreeMap<String, Integer> tm = new TreeMap<>(fx);
// 将map结构转换为string
StringBuilder sb = new StringBuilder();
while (!tm.isEmpty()) {
Map.Entry<String, Integer> entry = tm.pollFirstEntry();
sb.append(entry.getKey());
if (entry.getValue() != 1) sb.append(entry.getValue());
}
return sb.toString();
}
int idx = 0;// 访问字符串下标。
// 用栈来获取每个括号内的Map
public Map<String, Integer> count(String formula) {
Stack<Map<String, Integer>> sk = new Stack<>();
sk.push(new HashMap<>());
while (idx < formula.length()) {
char ch = formula.charAt(idx);
// 碰到左括号,往栈里压一个全新Map用于存储该层扩号里的原子信息。
if (ch == '(') {
sk.push(new HashMap<>());
++idx;
}
// 碰到右括号,把栈顶元素融入到上一个Map中。
else if (ch == ')') {
++idx;
mapFusion(sk.pop(), sk.peek(), getRightNum(formula));
}
else {
String key = getAtomName(formula);
Integer value = getRightNum(formula);
Map<String, Integer> fx = sk.peek();
fx.put(key, fx.getOrDefault(key, 0) + value);
}
}
return sk.peek();
}
// 将一个map的n倍融合到另一个map中。
public void mapFusion(Map<String, Integer> oldMap, Map<String, Integer> newMap, int n) {
Set<Map.Entry<String, Integer>> entries = oldMap.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
Integer value = entry.getValue() * n;
newMap.put(key, newMap.getOrDefault(key, 0) + value);
}
}
// 获得整个原子名称。
public String getAtomName(String formula) {
StringBuilder sb = new StringBuilder();
sb.append(formula.charAt(idx));
while (++idx < formula.length() && Character.isLowerCase(formula.charAt(idx)))
sb.append(formula.charAt(idx));
return sb.toString();
}
// 获取右侧原子的个数。
public int getRightNum(String formula) {
// 后面不带数字 || 后面无字符,默认为1.
if (idx == formula.length() || !Character.isDigit(formula.charAt(idx))) return 1;
// 循环判定 + 10倍扩数的方式。
int num = 0;
while (idx < formula.length() && Character.isDigit(formula.charAt(idx))) {
num = num * 10 + formula.charAt(idx) - '0';
++idx;
}
return num;
}
}
总结
1)括号匹配问题,掌握栈&递归遍历的基础,才能在此基础上做改进。
2)括号问题,三种情况,扩号嵌套/括号组合/括号组合&嵌套。
参考文献
[1] LeetCode 原子的数量