给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
**输入:** nums = [1,2,2]
**输出:** [[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
**输入:** nums = [0]
**输出:** [[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
前言 本题解基于「78. 子集的官方题解 」,请读者在充分理解该题解后继续阅读。
方法一:迭代法实现子集枚举 思路
考虑数组 $[1, 2, 2]$,选择前两个数,或者第一、三个数,都会得到相同的子集。
也就是说,对于当前选择的数 $x$,若前面有与其相同的数 $y$,且没有选择 $y$,此时包含 $x$ 的子集,必然会出现在包含 $y$ 的所有子集中。
我们可以通过判断这种情况,来避免生成重复的子集。代码实现时,可以先将数组排序;迭代时,若发现没有选择上一个数,且当前数字与上一个数相同,则可以跳过当前生成的子集。
代码
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution {public : vector<int > t; vector<vector<int >> ans; vector<vector<int >> subsetsWithDup (vector<int > &nums) { sort (nums.begin (), nums.end ()); int n = nums.size (); for (int mask = 0 ; mask < (1 << n); ++mask) { t.clear (); bool flag = true ; for (int i = 0 ; i < n; ++i) { if (mask & (1 << i)) { if (i > 0 && (mask >> (i - 1 ) & 1 ) == 0 && nums[i] == nums[i - 1 ]) { flag = false ; break ; } t.push_back (nums[i]); } } if (flag) { ans.push_back (t); } } return ans; } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { List<Integer> t = new ArrayList <Integer>(); List<List<Integer>> ans = new ArrayList <List<Integer>>(); public List<List<Integer>> subsetsWithDup (int [] nums) { Arrays.sort(nums); int n = nums.length; for (int mask = 0 ; mask < (1 << n); ++mask) { t.clear(); boolean flag = true ; for (int i = 0 ; i < n; ++i) { if ((mask & (1 << i)) != 0 ) { if (i > 0 && (mask >> (i - 1 ) & 1 ) == 0 && nums[i] == nums[i - 1 ]) { flag = false ; break ; } t.add(nums[i]); } } if (flag) { ans.add(new ArrayList <Integer>(t)); } } return ans; } }
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func subsetsWithDup (nums []int ) (ans [][]int ) { sort.Ints(nums) n := len (nums) outer: for mask := 0 ; mask < 1 <<n; mask++ { t := []int {} for i, v := range nums { if mask>>i&1 > 0 { if i > 0 && mask>>(i-1 )&1 == 0 && v == nums[i-1 ] { continue outer } t = append (t, v) } } ans = append (ans, append ([]int (nil ), t...)) } return }
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var subsetsWithDup = function (nums ) { nums.sort ((a, b ) => a - b); let t = [], ans = []; const n = nums.length ; for (let mask = 0 ; mask < (1 << n); ++mask) { t = []; let flag = true ; for (let i = 0 ; i < n; ++i) { if ((mask & (1 << i)) != 0 ) { if (i > 0 && (mask >> (i - 1 ) & 1 ) == 0 && nums[i] == nums[i - 1 ]) { flag = false ; break ; } t.push (nums[i]); } } if (flag) { ans.push (t.slice ()); } } return ans; };
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int ** subsetsWithDup (int * nums, int numsSize, int * returnSize, int ** returnColumnSizes) { qsort(nums, numsSize, sizeof (int ), cmp); int n = numsSize; *returnSize = 0 ; *returnColumnSizes = malloc (sizeof (int ) * (1 << n)); int ** ans = malloc (sizeof (int *) * (1 << n)); for (int mask = 0 ; mask < (1 << n); ++mask) { int * t = malloc (sizeof (int ) * n); int tSize = 0 ; bool flag = true ; for (int i = 0 ; i < n; ++i) { if (mask & (1 << i)) { if (i > 0 && (mask >> (i - 1 ) & 1 ) == 0 && nums[i] == nums[i - 1 ]) { flag = false ; break ; } t[tSize++] = nums[i]; } } t = realloc (t, sizeof (int ) * tSize); if (flag) { ans[*returnSize] = t; (*returnColumnSizes)[(*returnSize)++] = tSize; } } ans = realloc (ans, sizeof (int *) * (*returnSize)); return ans; }
复杂度分析
时间复杂度:$O(n \times 2^n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。排序的时间复杂度为 $O(n \log n)$。一共 $2^n$ 个状态,每种状态需要 $O(n)$ 的时间来构造子集,一共需要 $O(n \times 2^n)$ 的时间来构造子集。由于在渐进意义上 $O(n \log n)$ 小于 $O(n \times 2^n)$,故总的时间复杂度为 $O(n \times 2^n)$。
空间复杂度:$O(n)$。即构造子集使用的临时数组 $t$ 的空间代价。
方法二:递归法实现子集枚举 思路
与方法一类似,在递归时,若发现没有选择上一个数,且当前数字与上一个数相同,则可以跳过当前生成的子集。
代码
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution {public : vector<int > t; vector<vector<int >> ans; void dfs (bool choosePre, int cur, vector<int > &nums) { if (cur == nums.size ()) { ans.push_back (t); return ; } dfs (false , cur + 1 , nums); if (!choosePre && cur > 0 && nums[cur - 1 ] == nums[cur]) { return ; } t.push_back (nums[cur]); dfs (true , cur + 1 , nums); t.pop_back (); } vector<vector<int >> subsetsWithDup (vector<int > &nums) { sort (nums.begin (), nums.end ()); dfs (false , 0 , nums); return ans; } };
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { List<Integer> t = new ArrayList <Integer>(); List<List<Integer>> ans = new ArrayList <List<Integer>>(); public List<List<Integer>> subsetsWithDup (int [] nums) { Arrays.sort(nums); dfs(false , 0 , nums); return ans; } public void dfs (boolean choosePre, int cur, int [] nums) { if (cur == nums.length) { ans.add(new ArrayList <Integer>(t)); return ; } dfs(false , cur + 1 , nums); if (!choosePre && cur > 0 && nums[cur - 1 ] == nums[cur]) { return ; } t.add(nums[cur]); dfs(true , cur + 1 , nums); t.remove(t.size() - 1 ); } }
[sol2-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func subsetsWithDup (nums []int ) (ans [][]int ) { sort.Ints(nums) t := []int {} var dfs func (bool , int ) dfs = func (choosePre bool , cur int ) { if cur == len (nums) { ans = append (ans, append ([]int (nil ), t...)) return } dfs(false , cur+1 ) if !choosePre && cur > 0 && nums[cur-1 ] == nums[cur] { return } t = append (t, nums[cur]) dfs(true , cur+1 ) t = t[:len (t)-1 ] } dfs(false , 0 ) return }
[sol2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var subsetsWithDup = function (nums ) { nums.sort ((a, b ) => a - b); let t = [], ans = []; const dfs = (choosePre, cur, nums ) => { if (cur === nums.length ) { ans.push (t.slice ()); return ; } dfs (false , cur + 1 , nums); if (!choosePre && cur > 0 && nums[cur - 1 ] === nums[cur]) { return ; } t.push (nums[cur]); dfs (true , cur + 1 , nums); t = t.slice (0 , t.length - 1 ); } dfs (false , 0 , nums); return ans; };
[sol2-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 int cmp (int * a, int * b) { return *a - *b; } int * t;int tSize;void dfs (bool choosePre, int cur, int * nums, int numSize, int ** ret, int * returnSize, int ** returnColumnSizes) { if (cur == numSize) { int * tmp = malloc (sizeof (int ) * tSize); memcpy (tmp, t, sizeof (int ) * tSize); ret[*returnSize] = tmp; (*returnColumnSizes)[(*returnSize)++] = tSize; return ; } dfs(false , cur + 1 , nums, numSize, ret, returnSize, returnColumnSizes); if (!choosePre && cur > 0 && nums[cur - 1 ] == nums[cur]) { return ; } t[tSize++] = nums[cur]; dfs(true , cur + 1 , nums, numSize, ret, returnSize, returnColumnSizes); tSize--; } int ** subsetsWithDup (int * nums, int numsSize, int * returnSize, int ** returnColumnSizes) { qsort(nums, numsSize, sizeof (int ), cmp); int n = numsSize; *returnSize = 0 ; *returnColumnSizes = malloc (sizeof (int ) * (1 << n)); int ** ret = malloc (sizeof (int *) * (1 << n)); t = malloc (sizeof (int ) * n); dfs(false , 0 , nums, n, ret, returnSize, returnColumnSizes); return ret; }
复杂度分析
时间复杂度:$O(n \times 2^n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。排序的时间复杂度为 $O(n \log n)$。最坏情况下 $\textit{nums}$ 中无重复元素,需要枚举其所有 $2^n$ 个子集,每个子集加入答案时需要拷贝一份,耗时 $O(n)$,一共需要 $O(n \times 2^n)+O(n)=O(n \times 2^n)$ 的时间来构造子集。由于在渐进意义上 $O(n \log n)$ 小于 $O(n \times 2^n)$,故总的时间复杂度为 $O(n \times 2^n)$。
空间复杂度:$O(n)$。临时数组 $\textit{t}$ 的空间代价是 $O(n)$,递归时栈空间的代价为 $O(n)$。