Subsets

Given a set of distinct integers, nums, return all possible subsets.

Note:The solution set must not contain duplicate subsets.

For example,
If nums=[1,2,3], a solution is:

[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

思路分析

第一眼看到这道题就会感觉要穷举所nums数组中所有的数才能够得到子数组,然后加上题目中又要求return all posible subsets, 基本可以判断这道题会用到DFS,这是做这道题的初步的思路分析

然后关联第二类题型的template来实现,在python实现的时候,同一种template有两种实现方式

第一种实现方式是用一个临时数组来存放每一个subset,这样的话每一次调用递归后这个临时数组的内容就会发生变化(用append来实现添加新的element),因为数组在每一次递归的时候变化了,所以每次递归调用结束后还要把最后的一个元素pop出来进行回溯。另外将临时数组中存放的subset 添加到最终结果subsets数组时要生成新的对象(python中用'[ ]+'来实现),否则最终返回的结果将随着临时数组subset的变化而变化了。

第二种实现方式是用path路径的方式来实现,每次把新的遍历到的节点添加的path数组中。path也是一个list,但是它与subset的区别是什么呢?subset在每次递归的时候内容发生了变化(把新遍历到的节点append进list中了),而path在每次递归的时候内容没有发生变化。所以使用path路径的方式来实现时就不需要通过pop的方式来进行回溯了,那既然DFS是需要回溯的,path方式是怎样实现回溯的呢?答案是通过递归的方式,因为递归本身在走到path终点的时候就会回到来时的路

  • 通过临时数组subset的方式来实现
class Solution(object):
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        res = []
        self.dfs(sorted(nums), 0, [], res)
        return res

    def dfs(self, nums, index, subset, res):
        res.append([]+subset)
        for i in xrange(index, len(nums)):
            subset.append(nums[i])
            self.dfs(nums, i+1, subset, res)
            subset.pop()
  • 通过path路径的方式来实现
class Solution(object):
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        res = []
        self.dfs(nums, 0, [], res)
        return res

    def dfs(self, nums, index, path, res):
        res.append(path)
        for i in xrange(index, len(nums)):
            self.dfs(nums, i+1, path+[nums[i]], res)

源码分析

两种实现方式都是用DFS + 递归的方式来实现的,所使用的模版也是一样的。主要的区别是对数组的操作的不同。

既然是递归实现,那就要满足递归三要素,其中一个要素就是要有退出条件,即当递归到达某一种情况时不需要调用递归函数而直接可以得到解。乍一看这两种实现方式都没有一个显式的退出条件(if <> : return),但是仔细分析可以看出for循环在控制着递归函数调用的次数(不是无限循环),也就是说退出条件隐式在for循环中。

回溯法可以用图示和函数运行的堆栈图来理解,一般使用图形和递归的思想进行分析。下面以数组[1,2,3]为例来分析上面两种实现方式的图形递归堆栈图:

  • 通过临时数组subset的方式来实现

下图所示为subset和result随着栈的动态变化过程,箭头向下表示subset.append和result.append操作(进栈),箭头向上表示subset.pop (回溯/出栈)操作

  • 通过path路径的方式来实现

下图所示为subset和result随着栈的动态变化过程,箭头向下表示path数组的变化和result.append操作(进栈),箭头向上表示递归出栈过程

复杂度分析

时间复杂度

通过DFS递归实现的时间复杂度公式为:O(构造解的复杂度 * 解的个数)。每次递归里面有个for循环,所以每次构造解需要O(n)时间,那么总共有多少的解呢,2^n个,所以可以计算出总的时间复杂度为O(n*2^n)

空间复杂度

空间复杂度跟解的个数(递归调用的个数)是一直的,所以是O(2^n)

results matching ""

    No results matching ""