Subsets

Tags: Array, Backtracking, Bit Manipulation, Medium

Question

Problem Statement

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:

  1. [
  2. [3],
  3. [1],
  4. [2],
  5. [1,2,3],
  6. [1,3],
  7. [2,3],
  8. [1,2],
  9. []
  10. ]

题解

子集类问题类似Combination,以输入数组[1, 2, 3]分析,根据题意,最终返回结果中子集类的元素应该按照升序排列,故首先需要对原数组进行排序。题目的第二点要求是子集不能重复,至此原题即转化为数学中的组合问题。我们首先尝试使用 DFS 进行求解,大致步骤如下:

  1. [1] -> [1, 2] -> [1, 2, 3]
  2. [2] -> [2, 3]
  3. [3]

将上述过程转化为代码即为对数组遍历,每一轮都保存之前的结果并将其依次加入到最终返回结果中。

Iterative

Python

  1. class Solution:
  2. """
  3. @param S: The set of numbers.
  4. @return: A list of lists. See example.
  5. """
  6. def subsets(self, S):
  7. if not S:
  8. return []
  9. ret = []
  10. S.sort()
  11. n = len(S)
  12. # 000 -> []
  13. # 001 -> [1]
  14. # 010 -> [2]
  15. # ...
  16. # 111 -> [1, 2, 3]
  17. for i in xrange(2**n):
  18. tmp = []
  19. for j in xrange(n):
  20. if i & (1 << j):
  21. tmp.append(S[j])
  22. ret.append(tmp)
  23. return ret

源码分析

利用类似bit map的原理, 将 0 ~ 2^n - 1个数值map到每个index上,如果index数值为1,就将该number加入。比如输入是[1 ,2 ,3], 那么当i = 0时,0也就是000, 那么000 -> []; 当i = 1时, 001 -> [1]; 直到i = 7, 111 -> [1, 2, 3].

Recursive

Python

  1. class Solution:
  2. # @param {integer[]} nums
  3. # @return {integer[][]}
  4. def subsets(self, nums):
  5. if nums is None:
  6. return []
  7. result = []
  8. nums.sort()
  9. self.dfs(nums, 0, [], result)
  10. return result
  11. def dfs(self, nums, pos, list_temp, ret):
  12. # append new object with []
  13. ret.append([] + list_temp)
  14. for i in xrange(pos, len(nums)):
  15. list_temp.append(nums[i])
  16. self.dfs(nums, i + 1, list_temp, ret)
  17. list_temp.pop()

less code style

  1. class Solution:
  2. """
  3. @param S: The set of numbers.
  4. @return: A list of lists. See example.
  5. """
  6. def subsets(self, S):
  7. ret = []
  8. self.helper(sorted(S), ret, [])
  9. return ret
  10. def helper(self, vals, ret, tmp):
  11. ret.append(tmp[:])
  12. for i, val in enumerate(vals):
  13. self.helper(vals[i + 1:], ret, tmp + [val])

C++

  1. class Solution {
  2. public:
  3. vector<vector<int>> subsets(vector<int>& nums) {
  4. vector<vector<int> > result;
  5. if (nums.empty()) return result;
  6. sort(nums.begin(), nums.end());
  7. vector<int> list;
  8. dfs(nums, 0, list, result);
  9. return result;
  10. }
  11. private:
  12. void dfs(vector<int>& nums, int pos, vector<int> &list,
  13. vector<vector<int> > &ret) {
  14. ret.push_back(list);
  15. for (int i = pos; i < nums.size(); ++i) {
  16. list.push_back(nums[i]);
  17. dfs(nums, i + 1, list, ret);
  18. list.pop_back();
  19. }
  20. }
  21. };

Java

  1. public class Solution {
  2. public List<List<Integer>> subsets(int[] nums) {
  3. List<List<Integer>> result = new ArrayList<List<Integer>>();
  4. List<Integer> list = new ArrayList<Integer>();
  5. if (nums == null || nums.length == 0) {
  6. return result;
  7. }
  8. Arrays.sort(nums);
  9. dfs(nums, 0, list, result);
  10. return result;
  11. }
  12. private void dfs(int[] nums, int pos, List<Integer> list,
  13. List<List<Integer>> ret) {
  14. // add temp result first
  15. ret.add(new ArrayList<Integer>(list));
  16. for (int i = pos; i < nums.length; i++) {
  17. list.add(nums[i]);
  18. dfs(nums, i + 1, list, ret);
  19. list.remove(list.size() - 1);
  20. }
  21. }
  22. }

源码分析

Java 和 Python 的代码中在将临时list 添加到最终结果时新生成了对象,(Python 使用[] +), 否则最终返回结果将随着list 的变化而变化。

Notice: backTrack(num, i + 1, list, ret);中的『i + 1』不可误写为『pos + 1』,因为pos用于每次大的循环,i用于内循环,第一次写subsets的时候在这坑了很久… :(

回溯法可用图示和函数运行的堆栈图来理解,强烈建议使用图形和递归的思想分析,以数组[1, 2, 3]进行分析。下图所示为listresult动态变化的过程,箭头向下表示list.addresult.add操作,箭头向上表示list.remove操作。

Subsets运行递归调用图

复杂度分析

对原有数组排序,时间复杂度近似为 O(n \log n). 状态数为所有可能的组合数 O(2^n), 生成每个状态所需的时间复杂度近似为 O(1), 如[1] -> [1, 2], 故总的时间复杂度近似为 O(2^n).

使用了临时空间list保存中间结果,list 最大长度为数组长度,故空间复杂度近似为 O(n).

Reference