原子的数量[括号匹配问题]

216 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第38天,原子的数量[括号匹配问题] - 掘金 (juejin.cn)

前言

括号匹配问题,采用栈/递归来解决,定义全局变量idx,访问完整个字符串。掌握了这个基本功,才能在此基础上做一些复杂的事。

一、原子的数量

image.png

二、括号匹配

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 原子的数量