跳转至

1096. 花括号展开 II

来源第 142 场周赛 Q4难度困难分数2348

题目描述

如果你熟悉 Shell 编程,那么一定了解过花括号展开,它可以用来生成任意字符串。

花括号展开的表达式可以看作一个由 花括号、逗号 和 小写英文字母 组成的字符串,定义下面几条语法规则:

  • 如果只给出单一的元素 x,那么表达式表示的字符串就只有 "x"。R(x) = {x}
    • 例如,表达式 "a" 表示字符串 "a"。
    • 而表达式 "w" 就表示字符串 "w"。
  • 当两个或多个表达式并列,以逗号分隔,我们取这些表达式中元素的并集。R({e_1,e_2,...}) = R(e_1) ∪ R(e_2) ∪ ...
    • 例如,表达式 "{a,b,c}" 表示字符串 "a","b","c"。
    • 而表达式 "{{a,b},{b,c}}" 也可以表示字符串 "a","b","c"。
  • 要是两个或多个表达式相接,中间没有隔开时,我们从这些表达式中各取一个元素依次连接形成字符串。R(e_1 + e_2) = {a + b for (a, b) in R(e_1) × R(e_2)}
    • 例如,表达式 "{a,b}{c,d}" 表示字符串 "ac","ad","bc","bd"。
  • 表达式之间允许嵌套,单一元素与表达式的连接也是允许的。
    • 例如,表达式 "a{b,c,d}" 表示字符串 "ab","ac","ad"​​​​​​。
    • 例如,表达式 "a{b,c}{d,e}f{g,h}" 可以表示字符串 "abdfg", "abdfh", "abefg", "abefh", "acdfg", "acdfh", "acefg", "acefh"。

给出表示基于给定语法规则的表达式 expression,返回它所表示的所有字符串组成的有序列表。

假如你希望以「集合」的概念了解此题,也可以通过点击 “显示英文描述” 获取详情。

 

示例 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] 由 '{','}',',' 或小写英文字母组成
  • 给出的表达式 expression 用以表示一组基于题目描述中语法构造的字符串

解法

方法一:递归

思考

表达式含并、连接与多层花括号,求展开后的去重有序集合。表达式长 \(\le 60\),可递归把最内层花括号展开后拼回再处理。

找到第一个 } 及其匹配的 {,前缀 \(a\)、选项 \(b_i\)、后缀 \(c\) 拼成 \(a+b_i+c\) 继续递归;不再含括号则加入集合。

集合去重后排序输出。

我们设计一个递归函数 \(dfs(exp)\),用于处理表达式 \(exp\),并将结果存入集合 \(s\) 中。

对于表达式 \(exp\),我们首先找到第一个右花括号的位置 \(j\),如果找不到,说明 \(exp\) 中没有右花括号,即 \(exp\) 为单一元素,直接将 \(exp\) 加入集合 \(s\) 中即可。

否则,我们从位置 \(j\) 开始往左找到第一个左花括号的位置 \(i\),此时 \(exp[:i]\) 和 \(exp[j + 1:]\) 分别为 \(exp\) 的前缀和后缀,记为 \(a\) 和 \(c\)。而 \(exp[i + 1: j]\) 为 \(exp\) 中花括号内的部分,即 \(exp\) 中的子表达式,我们将其按照逗号分割成多个字符串 \(b_1, b_2, \cdots, b_k\),然后对每个 \(b_i\),我们将 \(a + b_i + c\) 拼接成新的表达式,递归调用 \(dfs\) 函数处理新的表达式,即 \(dfs(a + b_i + c)\)。

最后,我们将集合 \(s\) 中的元素按照字典序排序,即可得到答案。

时间复杂度 \(O(3^{n/6})\),空间复杂度 \(O(n 3^{n/7})\)。其中 \(n\) 为表达式 \(expression\) 的长度。最坏时间出现在 \(\{\ldots\{a,b,c\},a,b\}\) 这种逐层套上三路并集的表达式上:每增加 \(6\) 个字符,递归树就大约变成原来的 \(3\) 倍,各层字符串长度之和为 \(\Theta(3^{n/6})\)。连续拼接的 \(\{a,b,c\}\) 会生成 \(\Theta(3^{n/7})\) 个长度为 \(O(n)\) 的字符串,去重后的结果集占 \(O(n 3^{n/7})\) 空间,排序它们的开销是 \(O(n^2 3^{n/7})\),仍低于上面的时间上界。递归栈深度为 \(O(n)\),栈上字符串共 \(O(n^2)\)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Solution:
    def braceExpansionII(self, expression: str) -> List[str]:
        def dfs(exp):
            j = exp.find('}')
            if j == -1:
                s.add(exp)
                return
            i = exp.rfind('{', 0, j)
            a, c = exp[:i], exp[j + 1 :]
            for b in exp[i + 1 : j].split(','):
                dfs(a + b + c)

        s = set()
        dfs(expression)
        return sorted(s)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
    private TreeSet<String> s = new TreeSet<>();

    public List<String> braceExpansionII(String expression) {
        dfs(expression);
        return new ArrayList<>(s);
    }

    private void dfs(String exp) {
        int j = exp.indexOf('}');
        if (j == -1) {
            s.add(exp);
            return;
        }
        int i = exp.lastIndexOf('{', j);
        String a = exp.substring(0, i);
        String c = exp.substring(j + 1);
        for (String b : exp.substring(i + 1, j).split(",")) {
            dfs(a + b + c);
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
    vector<string> braceExpansionII(string expression) {
        dfs(expression);
        return vector<string>(s.begin(), s.end());
    }

private:
    set<string> s;

    void dfs(string exp) {
        int j = exp.find_first_of('}');
        if (j == string::npos) {
            s.insert(exp);
            return;
        }
        int i = exp.rfind('{', j);
        string a = exp.substr(0, i);
        string c = exp.substr(j + 1);
        stringstream ss(exp.substr(i + 1, j - i - 1));
        string b;
        while (getline(ss, b, ',')) {
            dfs(a + b + c);
        }
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func braceExpansionII(expression string) []string {
    s := map[string]struct{}{}
    var dfs func(string)
    dfs = func(exp string) {
        j := strings.Index(exp, "}")
        if j == -1 {
            s[exp] = struct{}{}
            return
        }
        i := strings.LastIndex(exp[:j], "{")
        a, c := exp[:i], exp[j+1:]
        for _, b := range strings.Split(exp[i+1:j], ",") {
            dfs(a + b + c)
        }
    }
    dfs(expression)
    ans := make([]string, 0, len(s))
    for k := range s {
        ans = append(ans, k)
    }
    sort.Strings(ans)
    return ans
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function braceExpansionII(expression: string): string[] {
    const dfs = (exp: string) => {
        const j = exp.indexOf('}');
        if (j === -1) {
            s.add(exp);
            return;
        }
        const i = exp.lastIndexOf('{', j);
        const a = exp.substring(0, i);
        const c = exp.substring(j + 1);
        for (const b of exp.substring(i + 1, j).split(',')) {
            dfs(a + b + c);
        }
    };
    const s: Set<string> = new Set();
    dfs(expression);
    return Array.from(s).sort();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use std::collections::BTreeSet;

impl Solution {
    pub fn brace_expansion_ii(expression: String) -> Vec<String> {
        fn dfs(exp: String, s: &mut BTreeSet<String>) {
            let Some(j) = exp.find('}') else {
                s.insert(exp);
                return;
            };
            let i = exp[..j].rfind('{').unwrap();
            let a = &exp[..i];
            let c = &exp[j + 1..];
            for b in exp[i + 1..j].split(',') {
                dfs(format!("{}{}{}", a, b, c), s);
            }
        }
        let mut s = BTreeSet::new();
        dfs(expression, &mut s);
        s.into_iter().collect()
    }
}

方法二:语法分析

思考

方法一每选中一个选项,就把还没展开的后缀再复制一份递归处理。像 \(\{\ldots\{a,b,c\},a,b\}\) 这样层层嵌套时,同一段后缀被重复扫描,时间达到 \(O(3^{n/6})\),不同单词却可能只有常数个。

逗号只出现在花括号里,表示并;括号外相邻的因子表示连接。按这个文法从左到右解析,每个子表达式只求一次集合。

因子是一段连续小写字母,或一对花括号中的表达式。连接把当前集合与因子集合做笛卡尔积,逗号把若干连接结果取并。哈希集合负责去重,全部解析完再按字典序排序。

记表达式为 \(s\)。从下标 \(i\) 出发定义两个函数。

\(\textit{expr}(i)\) 解析若干个 \(\textit{term}\) 的并集:先解析一个 \(\textit{term}\),若后面是逗号,就跳过逗号再解析下一个 \(\textit{term}\),把集合并进去,直到遇到 } 或结尾。

\(\textit{term}(i)\) 解析若干个因子的连接,初始集合为 \(\{\varepsilon\}\)。若当前字符是 {,则递归解析内部的 \(\textit{expr}\) 并跳过匹配的 };否则读出一段连续小写字母。然后把当前集合与该因子的集合做笛卡尔积。遇到逗号、} 或结尾时返回。

最后对 \(\textit{expr}(0)\) 的结果按字典序排序。同一段子表达式只解析一次,方法一里把后缀复制到每一支再重扫的开销不再出现。

时间复杂度 \(O(n^2 3^{n/7})\),空间复杂度 \(O(n 3^{n/7})\)。其中 \(n\) 为表达式长度。最坏情况是连续拼接的 {a,b,c},需要生成 \(\Theta(3^{n/7})\) 个长度为 \(O(n)\) 的字符串;构造这些字符串是 \(O(n 3^{n/7})\),排序是 \(O(n^2 3^{n/7})\)。嵌套的三路并集在本方法里每层集合大小为 \(O(1)\),时间只需 \(O(n)\)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution:
    def braceExpansionII(self, expression: str) -> List[str]:
        def expr(i: int):
            res, i = term(i)
            while i < len(expression) and expression[i] == ',':
                other, i = term(i + 1)
                res |= other
            return res, i

        def term(i: int):
            res = {''}
            while i < len(expression) and expression[i] not in ',}':
                if expression[i] == '{':
                    cur, i = expr(i + 1)
                    i += 1
                else:
                    j = i + 1
                    while j < len(expression) and expression[j].islower():
                        j += 1
                    cur = {expression[i:j]}
                    i = j
                res = {a + b for a in res for b in cur}
            return res, i

        ans, _ = expr(0)
        return sorted(ans)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Solution {
    private String exp;
    private int i;

    public List<String> braceExpansionII(String expression) {
        exp = expression;
        i = 0;
        List<String> ans = new ArrayList<>(expr());
        Collections.sort(ans);
        return ans;
    }

    private Set<String> expr() {
        Set<String> res = term();
        while (i < exp.length() && exp.charAt(i) == ',') {
            ++i;
            res.addAll(term());
        }
        return res;
    }

    private Set<String> term() {
        Set<String> res = new HashSet<>();
        res.add("");
        while (i < exp.length() && exp.charAt(i) != ',' && exp.charAt(i) != '}') {
            Set<String> cur = new HashSet<>();
            if (exp.charAt(i) == '{') {
                ++i;
                cur = expr();
                ++i;
            } else {
                int j = i + 1;
                while (j < exp.length() && exp.charAt(j) >= 'a' && exp.charAt(j) <= 'z') {
                    ++j;
                }
                cur.add(exp.substring(i, j));
                i = j;
            }
            Set<String> nxt = new HashSet<>();
            for (String a : res) {
                for (String b : cur) {
                    nxt.add(a + b);
                }
            }
            res = nxt;
        }
        return res;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Solution {
public:
    vector<string> braceExpansionII(string expression) {
        exp = std::move(expression);
        i = 0;
        set<string> ans = parseExpr();
        return vector<string>(ans.begin(), ans.end());
    }

private:
    string exp;
    int i = 0;

    set<string> parseExpr() {
        set<string> res = parseTerm();
        while (i < exp.size() && exp[i] == ',') {
            ++i;
            set<string> other = parseTerm();
            res.insert(other.begin(), other.end());
        }
        return res;
    }

    set<string> parseTerm() {
        set<string> res{""};
        while (i < (int) exp.size() && exp[i] != ',' && exp[i] != '}') {
            set<string> cur;
            if (exp[i] == '{') {
                ++i;
                cur = parseExpr();
                ++i;
            } else {
                int j = i + 1;
                while (j < (int) exp.size() && exp[j] >= 'a' && exp[j] <= 'z') {
                    ++j;
                }
                cur.insert(exp.substr(i, j - i));
                i = j;
            }
            set<string> nxt;
            for (const string& a : res) {
                for (const string& b : cur) {
                    nxt.insert(a + b);
                }
            }
            res.swap(nxt);
        }
        return res;
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
func braceExpansionII(expression string) []string {
    exp := expression
    i := 0
    var parseExpr func() map[string]struct{}
    var parseTerm func() map[string]struct{}
    parseExpr = func() map[string]struct{} {
        res := parseTerm()
        for i < len(exp) && exp[i] == ',' {
            i++
            for w := range parseTerm() {
                res[w] = struct{}{}
            }
        }
        return res
    }
    parseTerm = func() map[string]struct{} {
        res := map[string]struct{}{"": {}}
        for i < len(exp) && exp[i] != ',' && exp[i] != '}' {
            cur := map[string]struct{}{}
            if exp[i] == '{' {
                i++
                cur = parseExpr()
                i++
            } else {
                j := i + 1
                for j < len(exp) && exp[j] >= 'a' && exp[j] <= 'z' {
                    j++
                }
                cur[exp[i:j]] = struct{}{}
                i = j
            }
            nxt := map[string]struct{}{}
            for a := range res {
                for b := range cur {
                    nxt[a+b] = struct{}{}
                }
            }
            res = nxt
        }
        return res
    }
    all := parseExpr()
    ans := make([]string, 0, len(all))
    for w := range all {
        ans = append(ans, w)
    }
    sort.Strings(ans)
    return ans
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function braceExpansionII(expression: string): string[] {
    let i = 0;
    const expr = (): Set<string> => {
        const res = term();
        while (i < expression.length && expression[i] === ',') {
            ++i;
            for (const w of term()) {
                res.add(w);
            }
        }
        return res;
    };
    const term = (): Set<string> => {
        let res = new Set<string>(['']);
        while (i < expression.length && expression[i] !== ',' && expression[i] !== '}') {
            let cur: Set<string>;
            if (expression[i] === '{') {
                ++i;
                cur = expr();
                ++i;
            } else {
                let j = i + 1;
                while (j < expression.length && expression[j] >= 'a' && expression[j] <= 'z') {
                    ++j;
                }
                cur = new Set([expression.slice(i, j)]);
                i = j;
            }
            const nxt = new Set<string>();
            for (const a of res) {
                for (const b of cur) {
                    nxt.add(a + b);
                }
            }
            res = nxt;
        }
        return res;
    };
    return Array.from(expr()).sort();
}

评论