1. 问题背景与需求分析
LeetCode 1096题"花括号展开 II"是一个经典的字符串处理问题,要求我们实现一个能够解析嵌套花括号表达式并生成所有可能字符串组合的方法。这类问题在实际开发中经常遇到,比如配置文件解析、模板引擎实现、命令行参数扩展等场景。
题目给出的方法签名是:
java复制public List<String> braceExpansionII(String expression)
这个问题的核心在于处理两种操作:
- 并集操作:用逗号分隔的选项,如"{a,b}"表示"a"或"b"
- 笛卡尔积操作:连续出现的表达式,如"{a,b}{c,d}"表示"ac","ad","bc","bd"
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思路与算法设计
2.1 问题分解
这个问题可以分解为以下几个子问题:
- 如何识别并处理嵌套的花括号结构
- 如何正确区分并集(逗号分隔)和笛卡尔积(连续出现)
- 如何高效地生成所有可能的组合
2.2 递归下降解析法
我们可以采用递归下降的方法来解析表达式,这是处理嵌套结构的经典方法。基本思路是:
- 遇到'{'时,递归处理内部表达式
- 遇到','时,处理并集操作
- 遇到普通字符或子表达式结果时,处理笛卡尔积
2.3 栈辅助解法
另一种常见方法是使用栈来辅助处理嵌套结构:
- 维护两个栈:一个存操作数,一个存操作符
- 遇到'{'或','时入栈
- 遇到'}'时出栈并计算
3. 具体实现与代码解析
3.1 递归解法实现
java复制class Solution {
public List<String> braceExpansionII(String expression) {
List<List<String>> groups = new ArrayList<>();
groups.add(new ArrayList<>());
int level = 0;
int start = -1;
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == '{') {
if (level == 0) {
start = i + 1;
}
level++;
} else if (c == '}') {
level--;
if (level == 0) {
String sub = expression.substring(start, i);
List<String> subRes = braceExpansionII(sub);
List<String> last = groups.get(groups.size() - 1);
if (last.isEmpty()) {
last.addAll(subRes);
} else {
List<String> newLast = new ArrayList<>();
for (String s1 : last) {
for (String s2 : subRes) {
newLast.add(s1 + s2);
}
}
last.clear();
last.addAll(newLast);
}
}
} else if (c == ',' && level == 0) {
groups.add(new ArrayList<>());
} else if (level == 0) {
List<String> last = groups.get(groups.size() - 1);
if (last.isEmpty()) {
last.add(String.valueOf(c));
} else {
List<String> newLast = new ArrayList<>();
for (String s : last) {
newLast.add(s + c);
}
last.clear();
last.addAll(newLast);
}
}
}
// 合并所有组的并集
List<String> res = new ArrayList<>();
for (List<String> group : groups) {
res.addAll(group);
}
// 去重排序
res = new ArrayList<>(new TreeSet<>(res));
return res;
}
}
3.2 栈解法实现
java复制class Solution {
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> union = new ArrayList<>();
while (true) {
List<String> top = stack.pop();
union.addAll(top);
if (stack.peek().isEmpty()) {
stack.pop();
break;
}
}
List<String> product = new ArrayList<>();
if (stack.peek().isEmpty()) {
product = union;
} else {
for (String s1 : stack.peek()) {
for (String s2 : union) {
product.add(s1 + s2);
}
}
}
stack.peek().clear();
stack.peek().addAll(product);
} else if (c == ',') {
stack.push(new ArrayList<>());
} else {
List<String> top = stack.peek();
if (top.isEmpty()) {
top.add(String.valueOf(c));
} else {
List<String> newTop = new ArrayList<>();
for (String s : top) {
newTop.add(s + c);
}
top.clear();
top.addAll(newTop);
}
}
}
List<String> res = new ArrayList<>(new TreeSet<>(stack.pop()));
return res;
}
}
4. 复杂度分析与优化
4.1 时间复杂度
最坏情况下,时间复杂度为O(N×M),其中N是表达式长度,M是结果数量。这是因为对于每个字符,我们可能需要处理所有已生成的结果。
4.2 空间复杂度
空间复杂度主要取决于递归深度或栈的深度,最坏情况下为O(N),其中N是表达式长度。
4.3 优化方向
- 惰性求值:对于大规模结果集,可以考虑使用迭代器模式惰性生成结果
- 记忆化:对于重复的子表达式,可以缓存中间结果
- 并行处理:对于独立的并集分支,可以考虑并行计算
5. 测试用例与边界条件
5.1 基础测试用例
java复制@Test
public void testBasicCases() {
Solution solution = new Solution();
// 简单并集
assertEquals(Arrays.asList("a", "b"), solution.braceExpansionII("{a,b}"));
// 简单笛卡尔积
assertEquals(Arrays.asList("ac", "ad", "bc", "bd"),
solution.braceExpansionII("{a,b}{c,d}"));
// 混合情况
assertEquals(Arrays.asList("a", "ab", "ac", "b", "ba", "bc"),
solution.braceExpansionII("{a,b}c,{a,b}{a,b}"));
}
5.2 边界条件测试
java复制@Test
public void testEdgeCases() {
Solution solution = new Solution();
// 空表达式
assertEquals(Collections.emptyList(), solution.braceExpansionII(""));
// 单字符
assertEquals(Collections.singletonList("a"), solution.braceExpansionII("a"));
// 深层嵌套
assertEquals(Arrays.asList("ace", "acf", "ade", "adf", "bce", "bcf", "bde", "bdf"),
solution.braceExpansionII("{{a,b}{c,d}}{e,f}"));
// 重复结果
assertEquals(Arrays.asList("a", "b"), solution.braceExpansionII("{a,b}{a,b},a,b"));
}
6. 实际应用场景
6.1 配置文件解析
许多配置文件支持类似花括号展开的语法,用于生成多个配置项。例如:
code复制server.{dev,prod}.host = example.com
可以展开为:
code复制server.dev.host = example.com
server.prod.host = example.com
6.2 命令行工具
Unix shell中的花括号展开功能就是类似的实现:
code复制mkdir /usr/local/src/bash/{old,new,dist,bugs}
会创建四个目录。
6.3 模板引擎
模板引擎中经常需要处理类似的变量替换和组合逻辑,如:
code复制Hello {Mr,Ms}.{Smith,Jones}
7. 常见问题与调试技巧
7.1 嵌套层级过深
当表达式嵌套层级过深时,递归解法可能会导致栈溢出。解决方法:
- 改用栈实现的迭代解法
- 增加递归深度限制
- 优化算法减少递归深度
7.2 结果去重
题目要求结果需要去重并排序。常见错误:
- 忘记去重导致结果重复
- 去重时机不对影响后续计算
- 使用HashSet去重但结果顺序随机
解决方案:
java复制// 使用TreeSet自动排序去重
res = new ArrayList<>(new TreeSet<>(res));
7.3 性能优化
对于大规模输入,性能可能成为问题。优化建议:
- 避免不必要的中间结果复制
- 使用StringBuilder拼接字符串
- 提前计算并缓存重复子表达式结果
8. 扩展思考
8.1 支持更多操作符
可以扩展支持更多操作符,如:
- 范围表达式:{1..3} → 1,2,3
- 通配符匹配:
- 条件表达式:
8.2 惰性求值实现
对于可能产生大量结果的场景,可以实现惰性求值:
java复制interface Expression {
Iterator<String> evaluate();
}
class BraceExpression implements Expression {
// 实现惰性求值
}
8.3 与其他算法的结合
可以结合正则表达式、解析器生成器等工具,构建更强大的字符串处理能力。
