如果你熟悉 Shell 编程,那么一定了解过花括号展开,它可以用来生成任意字符串。
花括号展开的表达式可以看作一个由 花括号、逗号 和 小写英文字母 组成的字符串,定义下面几条语法规则:
- 如果只给出单一的元素
x
,那么表达式表示的字符串就只有"x"
。- 例如,表达式
{a}
表示字符串"a"
。 - 而表达式
{ab}
就表示字符串"ab"
。
- 例如,表达式
- 当两个或多个表达式并列,以逗号分隔时,我们取这些表达式中元素的并集。
- 例如,表达式
{a,b,c}
表示字符串"a","b","c"
。 - 而表达式
{a,b},{b,c}
也可以表示字符串"a","b","c"
。
- 例如,表达式
- 要是两个或多个表达式相接,中间没有隔开时,我们从这些表达式中各取一个元素依次连接形成字符串。
- 例如,表达式
{a,b}{c,d}
表示字符串"ac","ad","bc","bd"
。
- 例如,表达式
- 表达式之间允许嵌套,单一元素与表达式的连接也是允许的。
- 例如,表达式
a{b,c,d}
表示字符串"ab","ac","ad"
。 - 例如,表达式
{a{b,c}}{{d,e}f{g,h}}
可以代换为{ab,ac}{dfg,dfh,efg,efh}
,表示字符串"abdfg", "abdfh", "abefg", "abefh", "acdfg", "acdfh", "acefg", "acefh
"。
- 例如,表达式
给出表示基于给定语法规则的表达式 expression
,返回它所表示的所有字符串组成的有序列表。
假如你希望以「集合」的概念了解此题,也可以通过点击 “显示英文描述” 获取详情。
示例 1:
输入:"{a,b}{c{d,e}}"
输出:["acd","ace","bcd","bce"]
示例 2:
输入:"{{a,z}, a{b,c}, {ab,z}}"
输出:["a","ab","ac","z"]
解释:输出中 不应 出现重复的组合结果。
提示:
1 <= expression.length <= 50
expression[i]
由'{'
,'}'
,','
或小写英文字母组成- 给出的表达式
expression
用以表示一组基于题目描述中语法构造的字符串
解题思路
实际上这个问题和字节跳动2019笔试:字符串展开(超详细的解法!!!)有点类似,不过在这个问题比之前问题的难度更大,但是解题思路是一样的。
对于这种情况复杂的问题,我们不妨将其拆解为几个子问题处理。例如这题,我们需要实现
{a,b,c}
{a,b},{b,c}
{a,b}{c,d}
a{b,c,d}
{a{b,c}}{{d,e}f{g,h}}
第一个的实现非常简单
def f1(self, s1):
return list(set(s1[1:-1].split(",")))
第二个功能的实现只需调用第一个即可。
def f2(self, s2):
i = s2.find("}")+1
l = self.f1(s2[:i])
r = self.f1(s2[i+1:])
return list(set(l + r))
第三个功能的实现也需要调用第一个,然后计算笛卡尔积
def f3(self, s3):
i = s3.find("}")+1
l = self.f1(s3[:i])
r = self.f1(s3[i:])
return list("".join(s) for s in itertools.product(l, r))
第四个功能和第三个类似
def f4(self, s4):
i = s3.find("{")
l = s3[:i]
r = self.f1(s3[i:])
return list("".join(s) for s in itertools.product(l, r))
好,此时我们来处理递归结构,但是我们发现很难在一个递归结构中去判断上述的字符串。怎么办?说明我们问题切分的还不够细。实际上这个问题最本质在于是不是有逗号?如果有的话,我们左右元素做并集,否则做笛卡尔积,并且还有一点要注意的是笛卡尔积的优先级高。那么我们需要对上面的函数做一些修改。
我们先写两个基本操作函数。
def un(self, s):
return set().union(*s)
def pr(self, s):
return set("".join(i) for i in itertools.product(*s))
现在我们有了这两个工具那就好办了。接下来的问题就是处理递归,我们假设处理函数是 f f f的话,那么
- f ( x , y , z ) = f ( x ) ∪ f ( y ) ∪ f ( z ) f(x,y,z)=f(x)\cup f(y)\cup f(z) f(x,y,z)=f(x)∪f(y)∪f(z)
- f ( x y z ) = f ( x ) × f ( y ) × f ( z ) f(xyz)=f(x)\times f(y)\times f(z) f(xyz)=f(x)×f(y)×f(z)
我们定义函数 f 1 f_1 f1处理第一个情况, f 2 f_2 f2处理第二种情况。由于笛卡尔积的优先级高,所以我们在调用 f 1 f_1 f1函数前,需要先调用 f 2 f_2 f2,那么上面两个式子就可以转化为
- f 1 ( x , y , z ) = f 2 ( x ) ∪ f 2 ( y ) ∪ f 2 ( z ) f_1(x,y,z)=f_2(x)\cup f_2(y)\cup f_2(z) f1(x,y,z)=f2(x)∪f2(y)∪f2(z)
- f 2 ( x y z ) = f 1 ( x ) × f 1 ( y ) × f 1 ( z ) f_2(xyz)=f_1(x)\times f_1(y)\times f_1(z) f2(xyz)=f1(x)×f1(y)×f1(z)
那么下面的代码就顺理成章了
import itertools
class Solution:
def braceExpansionII(self, expression):
self.u, self.n = 0, len(expression)
return sorted(list(self.f2(expression)))
def f1(self, s1):
res = list()
while self.u < self.n:
if s1[self.u] == '}':
self.u += 1
break
if s1[self.u] == ',':
self.u += 1
continue
r = self.f2(s1)
res.append(r)
return self.un(res)
def f2(self, s2):
res = list()
while self.u < self.n:
if s2[self.u] == '{':
self.u += 1
r = self.f1(s2)
res.append(r)
elif s2[self.u].isalpha():
ts = ""
while self.u < self.n and s2[self.u].isalpha():
ts += s2[self.u]
self.u += 1
res.append({ts})
else:
break
return self.pr(res)
def un(self, s):
return set().union(*s)
def pr(self, s):
return set("".join(i) for i in itertools.product(*s))
还有一个比较清晰的思路就是对每个"{}"
对分层考虑,对于每一层我们先处理笛卡尔积然后处理并集操作。我们可以通过变量level
记录括号的层级信息,当我们碰到"{"
的时候,我们的level++
;当我们碰到"}"
,我们的level--
,当level==0
的时候就表示我们此时的一层遍历结束,我们需要递归处理这一层里面的表达式。例如
{ a , b } { c { d , e } }
l r
此时exp[l:r]
就是属于同一层级的表达式,我们需要递归处理exp[l:r]
里面的内容。
当level==0
并且遍历到的元素是","
的话,说明我们需要处理一个新的表达式了(并且这个表达式的层级和之前处理的是一致的);如果level==0
,但是遍历到的元素是字母的话,我们只需将字母添加到上一个表达式末尾即可。那么边界条件是什么呢?
边界条件就是要处理的字符串是空的时候,这个时候返回一个空的列表即可。
import itertools
class Solution:
def braceExpansionII(self, expression):
groups = [[]]
level = 0
for i, c in enumerate(expression):
if c == '{':
if level == 0:
start = i+1
level += 1
elif c == '}':
level -= 1
if level == 0:
groups[-1].append(self.braceExpansionII(expression[start:i]))
elif level == 0:
if c == ",":
groups.append([])
else:
groups[-1].append([c])
return sorted(set().union(*[set(map(''.join, itertools.product(*group))) for group in groups]))
代码非常的简洁。
reference:
https://leetcode.com/problems/brace-expansion-ii/discuss/317732/ChineseC%2B%2B-1096.
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!