Subsets - 子集

Question

  1. Given a set of distinct integers, return all possible subsets.
  2. Note
  3. Elements in a subset must be in non-descending order.
  4. The solution set must not contain duplicate subsets.
  5. Example
  6. If S = [1,2,3], a solution is:
  7. [
  8. [3],
  9. [1],
  10. [2],
  11. [1,2,3],
  12. [1,3],
  13. [2,3],
  14. [1,2],
  15. []
  16. ]

題解

子集類問題類似Combination,以輸入陣列[1, 2, 3]分析,根據題意,最終返回結果中子集類的元素應該按照升序排列,故首先需要對原陣列進行排序。題目的第二點要求是子集不能重複,至此原題即轉化為數學中的組合問題。我們首先嘗試使用 DFS 進行求解,大致步驟如下:

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

將上述過程轉化為程式碼即為對陣列遍歷,每一輪都保存之前的結果並將其依次加入到最終返回結果中。

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()

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