一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情。
前面我们讨论了笛卡尔积的递归算法实现。现在我们将要进一步的使用这个方法。
子集的生成
子集的问题实际上和笛卡尔积是同一个问题,因为子集实际上可以看作枚举 R ^ n ,其中 R 包括 0、1,而 n 代表集合元素的个数。
于是我们可以写出如下的代码:
def subset(n: int) -> List[List[int]]:
res = []
mask = [False] * n
def bt(d: int):
if d == n:
res.append([i for i, x in enumerate(mask) if x])
return
mask[d] = True
bt(d + 1)
mask[d] = False
bt(d + 1)
bt(0)
return res
其中 mask 表示已经被枚举的结果,相当于我们枚举的笛卡尔积。不过由于子集问题我们需要的是部分选择,从这个角度我们也可以认为只是选择一部分的数据,这和我们所谓的掩码是近似的概念,只需要一部分,不关注其他的部分。所以最终我把它命名了。
另一个值得注意的是,和与先前笛卡尔积不同的是,我们没有写成循环的形式,而是写了两份。
这里并不是因为它的分支少所以展开写或者使用循环等价。考虑的思路是对更具体的子集问题,实际上会有一些减枝操作。
而当前的这个位置做出决策时考虑的是不完全一样的,选择的时候我们更多的考虑他是否可以选择,而不选的时候更多地是考虑还能否有结果。这么说有些抽象,等到后序更具体地例子我们在解释相关的原因。
另外还有一点值得注意的是,对于回溯算法而言,最终的结果复杂度实际上比循环还要高一个量级。
比如说上面的子集生成算法,每个位置的选和不选构成n次操作。但是在每次生成子集时需要遍历整个子集,也需要遍历n个元素。最终复杂度的数量级在于。