一类字符串解析题目的思考
一类字符串解析题目的思考
相关题目
最近整理发现,某些机考场景比较喜欢对复杂字符串做解析,例如:
特征
其具体的表现为,给出一个字符串,给出一个基本结构字符串,例如{abc}
,是一个三明治(肉夹馍)结构,与扁平化json类似
进一步给出嵌套,例如{cd{abc}}
解析这一嵌套结构,给出输出
案例解析
以394. 字符串解码为例,将形如"3[a2[c]]"
解析为"accaccacc"
作为一道应用题,我们自然想到:从内层括号逐步向外解析,内层括号完成求值后传递给外层,是递归过程,描述为
- 解析最内层的
2[c]
,结果为cc
,传递回原字符,那么现在为3[acc]
- 解析最内层的
3[acc]
,结果为accaccacc
,传递回原字符,那么现在为accaccacc
- 没有层结构,结束
然而遗憾的是,这种替换字符串,在大多数语言中,代价都是巨大的,替换等价于重新构造字符串
但是上述过程体现了一种递归,我们从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();
}
}
案例解析
这道题目同样为嵌套结构,结构简单,增加了 倍数 和 命名规则两个,区分开后参考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';
}
}