1. 题目解析:花括号展开问题的本质
LeetCode 1096题"花括号展开II"是一个经典的字符串处理问题,要求我们实现一个能够解析嵌套花括号表达式的算法。题目给出的方法签名是public List<String> braceExpansionII(String expression),这意味着我们需要将一个包含花括号的字符串表达式展开成所有可能的组合。
这个问题的难点在于处理多层嵌套的花括号以及各种组合情况。比如表达式"{a,b}{c,{d,e}}"应该展开为["ac","ad","ae","bc","bd","be"]。在实际开发中,类似的需求经常出现在配置解析、模板引擎等场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思路分析
2.1 递归下降解析法
对于这种嵌套结构的解析,递归下降是最直观的解决方案。我们可以将表达式看作由多个"块"(block)组成,每个块可能是:
- 普通字符串
- 由逗号分隔的选项列表
- 嵌套的花括号表达式
处理流程大致如下:
- 遇到普通字符:直接作为当前块的一部分
- 遇到左花括号{:开始一个新的选项列表
- 遇到逗号:分隔选项
- 遇到右花括号}:结束当前选项列表,与前面的结果做笛卡尔积
2.2 栈辅助迭代法
递归方法虽然直观,但对于特别深的嵌套可能会导致栈溢出。我们可以使用栈来模拟递归过程:
- 维护两个栈:一个保存待处理的字符串(work),一个保存已处理的结果(res)
- 遇到{时,将当前work和res压栈,开始新的处理
- 遇到}时,弹出栈顶的work和res,进行合并
- 遇到,时,将当前work的内容作为选项加入res
这种方法更节省内存,适合处理大规模输入。
3. 代码实现详解
3.1 递归解法实现
java复制public List<String> braceExpansionII(String expression) {
List<String> res = new ArrayList<>();
dfs(expression, 0, new StringBuilder(), res);
return res;
}
private void dfs(String exp, int idx, StringBuilder path, List<String> res) {
if (idx == exp.length()) {
if (path.length() > 0) {
res.add(path.toString());
}
return;
}
char c = exp.charAt(idx);
if (c == '{') {
int end = findClosingBrace(exp, idx);
List<String> options = parseOptions(exp.substring(idx+1, end));
for (String opt : options) {
int len = path.length();
path.append(opt);
dfs(exp, end+1, path, res);
path.setLength(len);
}
} else {
path.append(c);
dfs(exp, idx+1, path, res);
path.setLength(path.length()-1);
}
}
3.2 迭代解法实现
java复制public List<String> braceExpansionII(String expression) {
Stack<List<String>> stack = new Stack<>();
stack.push(new ArrayList<>());
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == '{') {
stack.push(new ArrayList<>());
stack.push(new ArrayList<>());
} else if (c == '}') {
List<String> top = stack.pop();
List<String> merged = new ArrayList<>();
for (String s1 : stack.pop()) {
for (String s2 : top) {
merged.add(s1 + s2);
}
}
stack.peek().addAll(merged);
} else if (c == ',') {
stack.push(new ArrayList<>());
} else {
StringBuilder sb = new StringBuilder();
while (i < expression.length() && Character.isLetter(expression.charAt(i))) {
sb.append(expression.charAt(i++));
}
i--;
if (!stack.isEmpty()) {
stack.peek().add(sb.toString());
}
}
}
List<String> result = new ArrayList<>(new TreeSet<>(stack.pop()));
return result;
}
4. 关键算法细节
4.1 处理嵌套花括号
嵌套花括号是这个问题的主要难点。我们需要正确处理像"a{b,c{d,e}f}g"这样的表达式。递归解法中,findClosingBrace方法需要智能地找到匹配的右花括号:
java复制private int findClosingBrace(String s, int start) {
int count = 1;
for (int i = start + 1; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '{') count++;
else if (c == '}') count--;
if (count == 0) return i;
}
return -1;
}
4.2 处理选项列表
parseOptions方法需要处理可能包含嵌套的选项列表:
java复制private List<String> parseOptions(String s) {
List<String> options = new ArrayList<>();
int start = 0;
int level = 0;
for (int i = 0; i <= s.length(); i++) {
if (i == s.length() || (s.charAt(i) == ',' && level == 0)) {
String option = s.substring(start, i);
if (!option.isEmpty()) {
options.add(option);
}
start = i + 1;
} else if (s.charAt(i) == '{') {
level++;
} else if (s.charAt(i) == '}') {
level--;
}
}
return options;
}
5. 性能优化与边界情况
5.1 去重与排序
题目要求结果需要去重并按字典序排列。我们可以使用TreeSet来自然实现这两点:
java复制List<String> result = new ArrayList<>(new TreeSet<>(res));
5.2 处理空表达式
需要特别处理空输入或无效输入的情况:
java复制if (expression == null || expression.isEmpty()) {
return Collections.emptyList();
}
5.3 内存优化
对于大规模输入,递归解法可能导致栈溢出。迭代解法更节省内存,但代码更复杂。可以根据输入规模选择合适的解法。
6. 测试用例设计
完整的解决方案应该通过以下测试用例:
- 简单情况:"{a,b}c" → ["ac","bc"]
- 嵌套情况:"a{b,c{d,e}f}g" → ["abg","acdfg","acefg"]
- 多重嵌套:"{a,b}{c,{d,e}}" → ["ac","ad","ae","bc","bd","be"]
- 空选项:"a{,b}c" → ["ac","abc"]
- 复杂嵌套:"{a,b}c{d,e}f" → ["acdf","acef","bcdf","bcef"]
- 单字符:"a" → ["a"]
- 空输入:"" → []
7. 实际应用场景
这种花括号展开算法在实际开发中有广泛应用:
- 配置文件解析:支持灵活的配置选项组合
- 代码生成工具:根据模板生成多种变体
- 命令行工具:支持参数组合扩展
- 测试用例生成:自动生成多种输入组合
- 模板引擎:处理包含可选部分的模板
理解这个算法可以帮助我们更好地设计和实现需要灵活组合功能的系统。
8. 类似题目拓展
掌握这个解法后,可以尝试解决以下类似题目:
- LeetCode 1087.花括号展开I(简化版)
- LeetCode 726.原子的数量(复杂字符串解析)
- LeetCode 736.Lisp语法解析(嵌套表达式求值)
- LeetCode 385.迷你语法分析器(嵌套列表解析)
- LeetCode 439.三元表达式解析器(条件表达式解析)
这些题目都涉及复杂字符串解析和嵌套结构处理,解法思路有相通之处。
9. 编码技巧总结
- 对于嵌套结构,递归下降是最直观的解法
- 使用栈可以避免递归的深度限制问题
- 字符串处理时注意维护当前位置和临时结果
- 合理使用StringBuilder提高字符串拼接效率
- 注意处理边界条件和特殊输入
- 使用TreeSet自动处理去重和排序
- 复杂字符串解析时,可以分步骤编写辅助方法
在实际面试中,建议先解释清楚思路,再选择一种方法实现。如果时间允许,可以讨论另一种解法的优缺点。
