来源:力扣(LeetCode)
描述:
如果你熟悉 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"]
解释:输出中 不应 出现重复的组合结果。
提示:
方法:递归解析
思路与算法
表达式可以拆分为多个子表达式,以逗号分隔或者直接相接。我们应当先按照逗号分割成多个子表达式进行求解,然后再对所有结果求并集。这样做的原因是求积的优先级高于求并集的优先级。
我们用 expr 表示一个任意一种表达式,用 term 表示一个最外层没有逗号分割的表达式,那么 expr 可以按照如下规则分解:
其中的 ∣ 表示或者,即 expr 可以分解为前者,也可以分解为后者。
再来看 term, term 可以由小写英文字母或者花括号包括的表达式直接相接组成,我们用 item 来表示每一个相接单元,那么 term 可以按照如下规则分解:
item 可以进一步分解为小写英文字母 letter 或者花括号包括的表达式,它的分解如下:
在代码中,我们编写三个函数,分别负责以上三种规则的分解:
以下示意图以 {a,b}{c,{d,e}} 为例,展示了表达式递归拆解以及回溯的全过程。
在代码实现过程中有以下细节:
代码:
class Solution {string expression;int idx;// item -> letter | { expr }set item() {set ret;if (expression[idx] == '{') {idx++;ret = expr();} else {ret = {string(1, expression[idx])};}idx++;return move(ret);}// term -> item | item termset term() {// 初始化空集合,与之后的求解结果求笛卡尔积set ret = {""};// item 的开头是 { 或小写字母,只有符合时才继续匹配while (idx < expression.size() && (expression[idx] == '{' || isalpha(expression[idx]))) {auto sub = item();set tmp;for (auto &left : ret) {for (auto &right : sub) {tmp.insert(left + right);}}ret = move(tmp);}return move(ret);}// expr -> term | term, exprset expr() {set ret;while (true) {// 与 term() 求解结果求并集ret.merge(term());// 如果匹配到逗号则继续,否则结束匹配if (idx < expression.size() && expression[idx] == ',') {idx++;continue;} else {break;}}return move(ret);}public:vector braceExpansionII(string expression) {this->expression = expression;this->idx = 0;auto ret = expr();return {ret.begin(), ret.end()};}
};
执行用时:8 ms, 在所有 C++ 提交中击败了89.66%的用户
内存消耗:10 MB, 在所有 C++ 提交中击败了87.36%的用户
复杂度分析
时间复杂度:O(nlogn),其中 n 是 expression 的长度。整个
expression 只会遍历一次,时间复杂度为 O(n),集合合并以及求积运算的时间复杂度为 O(nlogn),因此总的时间复杂度为 O(nlogn)。
空间复杂度:O(n)。递归过程所需的栈空间为 O(n),以及存放中间答案的空间复杂度为 O(n),因此总的空间复杂度为 O(n)。
author:LeetCode-Solution
上一篇:SSM框架
下一篇:ip-guard智能终端常见问题