Hard

题目描述

根据下面给出的语法规则,字符串可以表示一组小写单词的集合。设 R(expr) 表示表达式所代表的单词集合。

通过简单的例子可以更好地理解语法规则:

  • 单个字母表示包含该单词的单例集合。

    • R(“a”) = {“a”}
    • R(“w”) = {“w”}
  • 当我们取两个或多个表达式的逗号分隔列表时,我们取可能性的并集。

    • R("{a,b,c}") = {“a”,“b”,“c”}
    • R("{{a,b},{b,c}}") = {“a”,“b”,“c”} (注意最终集合中每个单词最多只包含一次)
  • 当我们连接两个表达式时,我们取两个单词之间可能连接的集合,其中第一个单词来自第一个表达式,第二个单词来自第二个表达式。

    • R("{a,b}{c,d}") = {“ac”,“ad”,“bc”,“bd”}
    • R(“a{b,c}{d,e}f{g,h}”) = {“abdfg”, “abdfh”, “abefg”, “abefh”, “acdfg”, “acdfh”, “acefg”, “acefh”}

形式上,我们语法的三个规则是:

  1. 对于每个小写字母 x,我们有 R(x) = {x}。
  2. 对于表达式 e1, e2, … , ek(k >= 2),我们有 R({e1, e2, …}) = R(e1) ∪ R(e2) ∪ …
  3. 对于表达式 e1 和 e2,我们有 R(e1 + e2) = {a + b | (a, b) ∈ R(e1) × R(e2)},其中 + 表示连接,× 表示笛卡尔积。

给定一个表示根据给定语法的单词集合的表达式,返回表达式表示的单词的排序列表。

示例 1:

输入:expression = "{a,b}{c,{d,e}}"
输出:["ac","ad","ae","bc","bd","be"]

示例 2:

输入:expression = "{{a,z},a{b,c},{ab,z}}"
输出:["a","ab","ac","z"]
解释:每个不同的单词在最终答案中只写一次。

约束:

  • 1 <= expression.length <= 60
  • expression[i] 由 ‘{’, ‘}’, ‘,’ 或小写英文字母组成。
  • 给定的表达式根据描述中给出的语法表示一组单词。

解题思路

这道题需要解析并计算花括号表达式,主要涉及两种操作:

  1. 并集操作{a,b,c} 表示取 a、b、c 的并集
  2. 笛卡尔积操作:相邻表达式表示连接,如 {a,b}{c,d} = {“ac”,“ad”,“bc”,“bd”}

解题思路:

可以采用递归下降解析的方法。我们需要识别两种主要的语法结构:

  • 并集:用逗号分隔的表达式列表,用花括号包围
  • 连接:相邻的表达式直接连接

具体实现步骤:

  1. 解析单个块:遇到字母直接返回,遇到 { 则解析到对应的 }
  2. 处理并集:在花括号内按逗号分割,递归处理每部分后求并集
  3. 处理连接:连续的表达式块进行笛卡尔积连接
  4. 去重排序:最终结果需要去重并按字典序排序

算法采用递归解析,每次识别一个完整的表达式块,然后根据上下文决定是进行并集还是连接操作。使用集合数据结构自动处理去重,最后转换为排序列表。

推荐解法:递归下降解析法,清晰地处理语法规则,代码结构良好。

代码实现

class Solution {
private:
    int index;
    
    set<string> parseExpr(const string& expr) {
        set<string> result = parseItem(expr);
        
        while (index < expr.length() && expr[index] != '}' && expr[index] != ',') {
            set<string> next = parseItem(expr);
            set<string> newResult;
            for (const string& s1 : result) {
                for (const string& s2 : next) {
                    newResult.insert(s1 + s2);
                }
            }
            result = newResult;
        }
        
        return result;
    }
    
    set<string> parseItem(const string& expr) {
        set<string> result;
        
        if (expr[index] == '{') {
            index++; // skip '{'
            result = parseExpr(expr);
            
            while (index < expr.length() && expr[index] == ',') {
                index++; // skip ','
                set<string> next = parseExpr(expr);
                result.insert(next.begin(), next.end());
            }
            
            index++; // skip '}'
        } else {
            result.insert(string(1, expr[index]));
            index++;
        }
        
        return result;
    }
    
public:
    vector<string> braceExpansionII(string expression) {
        index = 0;
        set<string> resultSet = parseExpr(expression);
        return vector<string>(resultSet.begin(), resultSet.end());
    }
};
class Solution:
    def braceExpansionII(self, expression: str) -> List[str]:
        def parse_expr():
            result = parse_item()
            
            while self.index < len(expression) and expression[self.index] not in '}':
                if expression[self.index] == ',':
                    break
                next_set = parse_item()
                new_result = set()
                for s1 in result:
                    for s2 in next_set:
                        new_result.add(s1 + s2)
                result = new_result
            
            return result
        
        def parse_item():
            result = set()
            
            if expression[self.index] == '{':
                self.index += 1  # skip '{'
                result = parse_expr()
                
                while self.index < len(expression) and expression[self.index] == ',':
                    self.index += 1  # skip ','
                    next_set = parse_expr()
                    result.update(next_set)
                
                self.index += 1  # skip '}'
            else:
                result.add(expression[self.index])
                self.index += 1
            
            return result
        
        self.index = 0
        result_set = parse_expr()
        return sorted(list(result_set))
public class Solution {
    private int index;
    
    public IList<string> BraceExpansionII(string expression) {
        index = 0;
        var resultSet = ParseExpr(expression);
        var result = new List<string>(resultSet);
        result.Sort();
        return result;
    }
    
    private HashSet<string> ParseExpr(string expr) {
        var result = ParseItem(expr);
        
        while (index < expr.Length && expr[index] != '}' && expr[index] != ',') {
            var next = ParseItem(expr);
            var newResult = new HashSet<string>();
            foreach (var s1 in result) {
                foreach (var s2 in next) {
                    newResult.Add(s1 + s2);
                }
            }
            result = newResult;
        }
        
        return result;
    }
    
    private HashSet<string> ParseItem(string expr) {
        var result = new HashSet<string>();
        
        if (expr[index] == '{') {
            index++; // skip '{'
            result = ParseExpr(expr);
            
            while (index < expr.Length && expr[index] == ',') {
                index++; // skip ','
                var next = ParseExpr(expr);
                foreach (var item in next) {
                    result.Add(item);
                }
            }
            
            index++; // skip '}'
        } else {
            result.Add(expr[index].ToString());
            index++;
        }
        
        return result;
    }
}
var braceExpansionII = function(expression) {
    let index = 0;
    
    function parseExpr() {
        let result = parseItem();
        
        while (index < expression.length && expression[index] !== '}' && expression[index] !== ',') {
            let next = parseItem();
            let newResult = new Set();
            for (let s1 of result) {
                for (let s2 of next) {
                    newResult.add(s1 + s2);
                }
            }
            result = newResult;
        }
        
        return result;
    }
    
    function parseItem() {
        let result = new Set();
        
        if (expression[index]

复杂度分析

复杂度类型复杂度说明
时间复杂度O(N × M)N 是表达式长度,M 是最终结果集的大小。在最坏情况下需要生成所有可能的字符串组合
空间复杂度O(M)M 是最终结果集的大小,用于存储所有可能的字符串

相关题目