Leetcode 90. 子集 II

对于包含 \(n\) 个元素的集合(其中每个元素互不相同),我们容易得到其子集数量为 \(2^n\)。由下面的DFS搜索树可以看到,依次判断每个元素是否加入到当前子集中,直到递归到叶子节点则得到全部 \(2^n\) 个子集。

本问题将输入由集合扩展为可能保护重复元素的数组,仍然求解其所有子集。比较naive的方法是同样先得到全部 \(2^n\) 个子集再去重,但这会增加额外的比较时间和内存消耗。

DFS+剪枝

分析搜索树,我们可以发现,子集重复情况出现在:当连续相同元素在一个分支上,前一次没有选择,而后一次继续选择该元素时发生。例如 输入 nums = [1, 1], 则搜索树的第三个分支应该剪掉。

image

具体代码实现,需要先对输入排序,新加入元素时,进行重复检查进行剪枝。

pos>0 and nums[pos-1]==nums[pos] and not lastChoosed

由于我脑抽,一开始对上次选择 直接使用 not last==nums[pos-1] 进行判断 debug 好久都没AC,因为这样对于连续 3 个相同元素,当搜索到第三个元素时,无法区分第二个元素未选的情况,这样会得到 {1, 1, x}, {1, x, 1} 两组重复答案。
正确的写法如下:

def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
    nums.sort()
    n = len(nums)
    ans = []

    def dfs(pos, cand, lastChoosed):
        if pos==n:
            ans.append(cand)
        return

        if not (pos>0 and nums[pos-1]==nums[pos] and not lastChoosed):
            dfs(pos+1, cand+[nums[pos]], nums[pos])

        dfs(pos+1, cand, False)

    dfs(0, [], False)
    return ans

另一种思路

查看题解,还有另一种解法。

将搜索过程中的状态看作一个子集,我们不断将未选择的元素加入到已选的状态中。那么对于同一层的分支,相同元素的搜索分支应该被剪掉(相当于前面解法前一次未选,后一次仍然选择该元素,这种情况等价于先选择该元素之后不选,而这个分支已经搜索过)。而不同层次则可以重复选择相同元素。

引用Leetcode上的图片说明

image

题解代码还能进一步精简:对于 pos 以前的元素为已选的,每次只用选择相邻不同的元素加入候选集中

def dfs(pos, cand):
	ans.append(cand)
    if pos==n:
    	return

	for k in range(pos, n):
		if k==pos or nums[k]!=nums[k-1]:
			dfs(k+1, cand+[nums[k]])

dfs(0, [])

---(完)---

posted @ 2021-09-20 17:01  izcat  阅读(48)  评论(0编辑  收藏  举报