今天这一题是求幂集。小学数学都忘得差不多了… 幂集是什么?
幂集(power set)是一个集合的所有子集。比如[1, 2, 3]的幂集就是:
[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
不过这道题有一个额外的要求:
在求幂集时要以“深度优先搜索”(depth-first search)的形式进行。也就是说,对于集合的每个元素,都有“取”和“舍”两种选择;在深度优先时,我们优先选择“舍”。仍以[1, 2, 3]为例,在这种情况下,它的幂集应该是:
[[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
现在给定一个列表,如何以深度优先遍历的方法找出它的幂集?
这道题有两种思路。第一种,是采用回溯法。用白话说,就是在每一个分叉路口,偏执的选择一条道走到黑,此时再返回至上一个路口选择另外一条道继续… 这个过程相当于下面这个二叉树:
用递归实现非常简单:用对应输入集合列表的另一个列表res保存当前这条路上每个元素是取还是舍,然后继续往前走直到列表末尾,最后输出这条路上所有是“取”的元素。实现时为简单起见使用了嵌套函数。
def powerset(nums): powerset = [] res = [0] * len(nums) def visit(i): if i == len(nums): powerset.append([num for j, num in enumerate(nums) if res[j] == 1]) # 输出这条路上所有是“取”的元素 else: res[i] = 0 visit(i+1) # 先“舍”,继续往前走 res[i] = 1 visit(i+1) # 再“取”,继续往前走 visit(0) return powerset
另外一种思路是观察:
[1, 2, 3] -> [[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
幂集是不是相当于[1, 2, 3]按照[0, 0, 0], [0, 0, 1], … [1, 1, 1]选择出来的?而[0, 0, 0], [0, 0, 1], … [1, 1, 1]既可以看成0-7的二进制表达,也可以看作是3个(0, 1)的笛卡尔积。
实现要点:
1. 对一个集合按照selector进行选择:
用itertools的compress方法。
2. 将十进制数转换为前面补0的固定长度的二进制数:
用format函数。
3. 求多个集合的笛卡尔积:
用itertools的product方法。
借用某个最优解法如下:
from itertools import product, compress def powerset(nums): return [list(compress(nums, pattern)) for pattern in product((0, 1), repeat=len(nums))]
其实,如果没有深度优先的要求,求一个集合的幂集是非常简单的,用itertools模块的combinations方法就可以轻松做到:
from itertools import combinations def power(s): return [list(c) for i in range(len(s)+1) for c in combinations(s, i)]
解决一个问题的方法多多,最重要是善于观察和总结,对吗?