78. 子集

Posted 炫云云

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了78. 子集相关的知识,希望对你有一定的参考价值。

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

输入:nums = [0]
输出:[[],[0]]

迭代法

记原序列中元素的总数为 n 0 n_{0} n0 原序列中的每个数字 a i a_{i} ai 的状态可能有两种,即「在子集中」和「不在 子集中」。我们用 1 表示「在子集中」, 0 表示不在子集中,那么每一个子集可以对应一个长度为 n n n 0 / 1 0 / 1 0/1 序列,第 i i i 位表示 a i a_{i} ai 是否在子集中。例如, n = 3 , a = { 5 , 2 , 9 } n=3, a=\\{5,2,9\\} n=3,a={5,2,9} 时:

可以发现 0 / 1 0 / 1 0/1 序列对应的二进制数正好从 0 到 2 n − 1 2^{n}-1 2n1 。我们可以枚举 mask ∈ [ 0 , 2 n − 1 ] \\in\\left[0,2^{n}-1\\right] [0,2n1], mask 的二 进制表示是一个 0 / 1 0 / 1 0/1 序列,我们可以按照这个 0 / 1 0 / 1 0/1 序列在原集合当中取数。当我们枚举完所有 2 n 2^{n} 2n 个 mask, 我们也就能构造出所有的子集。

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans = []
        n = len(nums)
        for mask in range(1<<n): #子集数量 2^n
            t  = []
            for i in range(n): #对每个二进制数也就是每种子集情况都要遍历一边nums数组,
                if mask &(1<<i): # 将二进制数中为1的位置在nums中对应的数加入到当前子集中
                    t.append(nums[i])
            ans.append(t)
        return ans

回溯算法

当问题需要 “回头”,以此来查找出所有的解的时候,使用回溯算法。即满足结束条件或者发现不是正确路径的时候(走不通),要撤销选择,回退到上一个状态,继续尝试,直到找出所有解为止。

具体请看:回溯算法 python

该题的回溯算法树:

  • [1, 2, 3]为例说明一下回溯的过程 :先看1,选1或不选1,都会再看2,选2或不选2,以此类推。
  1. 什么都不选,加入空数组[]

  2. 选1 :[1]

    • 选2: [1,2]
      • 选3 [1,2,3]
    • 不选2,选3: [1,3]
  3. 不选1,选2:[2]

      • 选3 [2,3]

    选3 [3]

用索引index代表当前递归考察的数字nums[index]

index越界时,说明所有数字考察完了,得到一个解,把它加入解集,结束当前递归分支。

在进入 b a c k t r a c k ( i n d e x ) backtrack(index) backtrack(index)之前 [ 0 , i n d e x − 1 ] [0,index−1] [0,index1] 位置的状态是确定的,而 [ i n d e x , n − 1 ] [index,n−1] [index,n1] 内位置的状态是不确定的, b a c k t r a c k ( i n d e x ) backtrack(index) backtrack(index) 需要确定 i n d e x index index位置的状态,然后求解子问题 b a c k t r a c k ( i n d e x + 1 ) backtrack(index+1) backtrack(index+1)

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans = []
        tem = []
        n = len(nums)
        def backtrack(index = 0):
            """
            """
            if index == n:# 所有数都填完了
                ans.append(tem[:])
                return
            # 每位上的数字都有选和不选两种 不选:tem+[]; 选:tem.append(nums[index])
            tem.append(nums[index])
            backtrack(index+1)
            tem.pop()
            backtrack(index+1) #不取
        backtrack(0)
        return ans

刚才的思路是:逐个考察数字,每个数都选或不选。等到递归结束时,把集合加入解集。

换一种思路:在执行子递归之前,加入解集,即,在递归压栈前 “做事情”。

该题的回溯算法树:

  • [1, 2, 3]为例说明一下回溯的过程
  1. 加入空数组[]
  2. 枚举长度为1的子集:[1] | [2] | [3]
  3. 在长度为1的子集基础上递归进入枚举长度为2的子集: [1,2] [1,3] | [2,3]
  4. 在长度为2的子集基础上递归进入枚举长度为3的子集: [1,2,3]
class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        n = len(nums)
        
        def backtrack(index, tem):
            res.append(tem[:])
            for j in range(index, n):
                tem.append(nums[j])
                backtrack(j + 1,tem)
                tem.pop()
        backtrack(0, [])
        return res  

参考

Krahets - 力扣(LeetCode) (leetcode-cn.com)

以上是关于78. 子集的主要内容,如果未能解决你的问题,请参考以下文章

78子集

[leetcode] 78. 子集

[LeetCode] 78. 子集 ☆☆☆(回溯)

78. 子集

精选力扣500题 第61题 LeetCode 78. 子集c++/java详细题解

[算法题解详细]DFS解力扣78子集