一类字符串解析题目的思考

发布时间 2023-08-28 19:48:46作者: jentreywang

一类字符串解析题目的思考

相关题目

最近整理发现,某些机考场景比较喜欢对复杂字符串做解析,例如:

  1. 394. 字符串解码

  2. 1190. 反转每对括号间的子串

  3. 726. 原子的数量

特征

其具体的表现为,给出一个字符串,给出一个基本结构字符串,例如{abc},是一个三明治(肉夹馍)结构,与扁平化json类似

进一步给出嵌套,例如{cd{abc}}

解析这一嵌套结构,给出输出

案例解析

394. 字符串解码为例,将形如"3[a2[c]]"解析为"accaccacc"

作为一道应用题,我们自然想到:从内层括号逐步向外解析,内层括号完成求值后传递给外层,是递归过程,描述为

  1. 解析最内层的 2[c],结果为cc,传递回原字符,那么现在为3[acc]
  2. 解析最内层的3[acc],结果为accaccacc,传递回原字符,那么现在为accaccacc
  3. 没有层结构,结束

然而遗憾的是,这种替换字符串,在大多数语言中,代价都是巨大的,替换等价于重新构造字符串

但是上述过程体现了一种递归,我们从2->1来看,就是解决外层括号问题,可以由内层括号问题的解决而解决...

因此这类题目,我的习惯是使用一个递归函数,一次遍历,遇到括号则递归调用,调用的返回值为我需要的结果,拼接即可

仍然以394. 字符串解码为例

给出核心代码

这个代码写的比较乱,毕竟题目比较简单,如果是困难的题目,字符判断最好抽象为方法

class Solution {
    public String decodeString(String ss) {
        char[] s = ss.toCharArray();
        int n = s.length;
        // 入口只需要调用一次递归函数
        return decodeChars(s, 0, n-1);
    }

    // 核心递归函数,返回值为 s[start...stop] 区间的计算结论
    private String decodeChars(char[] s, int start, int stop) {
        if (start > stop) {
            return "";
        }
        int times = 0;
        int i = start;
        StringBuilder sb = new StringBuilder();
        if (s[start] < '0' || s[start] > '9') { // 无数字
            while (i <= stop && s[i] >= 'a' && s[i] <= 'z') {
                sb.append(s[i]);
                i++;
            }
            if (i <= stop) {
                sb.append(
                    decodeChars(s, i, stop)
                );
            }
        } else { // 有数字
            while (i < stop && s[i] != '[') {
                times *= 10;
                times += s[i] - '0';
                i++;
            }
            // [ ]
            // i ?
            i++;
            // 匹配左右 []
            int counter = 1;
            int j = i+1;
            while (j <= stop) {
                if (s[j] == ']') {
                    counter--;
                    if (counter == 0) {
                        break;
                    }
                } else if (s[j] == '[') {
                    counter++;
                }
                j++;
            } 
            // [ ..  ]
            //   i   j
            String inner = decodeChars(s, i, j-1);
            for (int k = 0; k < times; k++) {
                sb.append(inner);
            }
            sb.append(
                decodeChars(s, j+1, stop)
            );
        }
        return sb.toString();
    }
}

案例解析

726. 原子的数量

这道题目同样为嵌套结构,结构简单,增加了 倍数 和 命名规则两个,区分开后参考394,主体是一致的

为了简洁,也可以抽象 get name 、get cnt 为方法

class Solution {
    public String countOfAtoms(String formula) {
        char[] s = formula.toCharArray();
        int n = s.length;

        // 主函数同样只调用一次递归方法
        Map<String, Integer> map = count(s, 0, n-1);

        StringBuilder sb = new StringBuilder();
        ArrayList<String> sortKey = new ArrayList<>(map.keySet());
        sortKey.sort(String::compareTo);
        for (String key: sortKey) {
            int cnt = map.get(key);
            sb.append(key);
            if (cnt > 1) {
                sb.append(map.get(key));
            }
        }

        return sb.toString();
    }

    // 核心递归方法
    private Map<String, Integer> count(char[] s, int start, int stop) {
        Map<String, Integer> map = new HashMap<>();
        while (start <= stop) {
            char curr = s[start];
            if (curr == '(') {// 模拟栈寻找 )
                int l = 1;
                int i = start+1;
                while (i <= stop && l > 0) {
                    if (s[i] == '(') l++;
                    else if (s[i] == ')') l--;
                    i++;
                }
                // 递归调用
                Map<String, Integer> subMap = count(s, start+1, i-2);
                int cnt = 0;
                if (i <= stop) {
                    while (i <= stop && isDigits(s[i])) {
                        cnt *= 10;
                        cnt += s[i] - '0';
                        i++;
                    }
                    if (cnt > 0) { // 仅当 cnt > 0, (!=1) 时才做倍数运算
                        for (String key : subMap.keySet()) {
                            subMap.put(key, subMap.get(key) * cnt);
                        }
                    }
                }
                // 与当前map合并
                union(map, subMap);
                start = i;
                continue;
            }

            // get name
            int i = start+1;
            while (i <= stop && isLower(s[i])) {
                i++;
            }
            String name = new String(s, start, i-start);
            // get cnt
            int cnt = 0;
            if (i > stop || !isDigits(s[i])) {
                cnt = map.getOrDefault(name, 0) + 1;
                map.put(name, cnt);
            } else if (isDigits(s[i])) {
                while (i <= stop && isDigits(s[i])) {
                    cnt *= 10;
                    cnt += s[i] - '0';
                    i++;
                }
                cnt += map.getOrDefault(name, 0);
                map.put(name, cnt);
            }
            start = i;
        }
        return map;
    }

    // source <- target, 合并两个map
    private void union(Map<String, Integer> source, Map<String, Integer> target) {
        target.forEach((k,v) -> {
            v += source.getOrDefault(k, 0);
            source.put(k, v);
        });
    }

    private boolean isUpper(char c) {
        return c >= 'A' && c <= 'Z';
    }

    private boolean isLower(char c) {
        return c >= 'a' && c <= 'z';
    }

    private boolean isDigits(char c) {
        return c >= '0' && c <= '9';
    }
}