給定一組不含重復(fù)元素的整數(shù)數(shù)組 nums,返回該數(shù)組所有可能的子集(冪集)耐床。
說明:解集不能包含重復(fù)的子集。
示例:
輸入: nums = [1,2,3]
輸出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
解法一:
這道求子集合的問題楔脯,由于其要列出所有結(jié)果撩轰,按照以往的經(jīng)驗,肯定要是要用遞歸來做昧廷。這道題其實它的非遞歸解法相對來說更簡單一點堪嫂,下面我們先來看非遞歸的解法,我們可以一位一位的往上疊加木柬,比如對于題目中給的例子 [1,2,3] 來說溉苛,最開始是空集,那么我們現(xiàn)在要處理 1弄诲,就在空集上加 1愚战,為 [1],現(xiàn)在我們有兩個子集 [] 和 [1]齐遵,下面我們來處理 2寂玲,我們在之前的子集基礎(chǔ)上,每個都加個 2梗摇,可以分別得到 [2]拓哟,[1, 2],那么現(xiàn)在所有的子集合為 []伶授,[1]断序,[2],[1, 2]糜烹,同理處理 3 的情況可得 [3]违诗,[1, 3],[2, 3]疮蹦,[1, 2, 3]诸迟,再加上之前的子集就是所有的子集合了,代碼如下:
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
lists.add(new ArrayList<>());
for (int i = 0; i < nums.length; i++) {
int size = lists.size();
for (int j = 0; j < size; j++) {
List<Integer> list = lists.get(j);
list.add(nums[i]);
lists.add(new ArrayList<>(list));
//System.out.println("before: " + list);
list.remove(list.size() - 1);
//System.out.println("after: " + list);
}
}
return lists;
}
打印迭代過程如下:
before: [1]
after: []
before: [2]
after: []
before: [1, 2]
after: [1]
before: [3]
after: []
before: [1, 3]
after: [1]
before: [2, 3]
after: [2]
before: [1, 2, 3]
after: [1, 2]
解法二
下面來看遞歸的解法愕乎,這相當(dāng)于一種深度優(yōu)先搜索阵苇,參見網(wǎng)友JustDoIt 的博客,由于原集合每一個數(shù)字只有兩種狀態(tài)感论,要么存在绅项,要么不存在,那么在構(gòu)造子集時就有選擇和不選擇兩種情況比肄,所以可以構(gòu)造一棵二叉樹快耿,左子樹表示選擇該層處理的節(jié)點囊陡,右子樹表示不選擇,最終的葉節(jié)點就是所有子集合润努,樹的結(jié)構(gòu)如下:
[]
/ \
/ \
/ \
[1] []
/ \ / \
/ \ / \
[1 2] [1] [2] []
/ \ / \ / \ / \
[1 2 3] [1 2] [1 3] [1] [2 3] [2] [3] []
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
List<Integer> out = new ArrayList<>();
subsetsDFS(nums, 0, out, lists);
return lists;
}
public void subsetsDFS(int[] nums, int level, List<Integer> out, List<List<Integer>> lists) {
lists.add(out);
for (int i = level; i < nums.length; i++) {
out.add(nums[i]);
List<Integer> list = new ArrayList<>(out);
subsetsDFS(nums, i + 1, list, lists);
out.remove(out.size() - 1);
}
}
子集添加的順序如下:
[]
[1]
[1, 2]
[1, 2, 3]
[1, 3]
[2]
[2, 3]
[3]
解法三
最后我們再來看一種解法关斜,這種解法是 CareerCup 書上給的一種解法,想法也比較巧妙铺浇,把數(shù)組中所有的數(shù)分配一個狀態(tài)痢畜,true 表示這個數(shù)在子集中出現(xiàn),false 表示在子集中不出現(xiàn)鳍侣,那么對于一個長度為 n 的數(shù)組丁稀,每個數(shù)字都有出現(xiàn)與不出現(xiàn)兩種情況,所以共有 2^n 種情況倚聚,我們把每種情況都轉(zhuǎn)換出來就是子集了线衫,我們還是用題目中的例子, [1 2 3] 這個數(shù)組共有 8 個子集,每個子集的序號用二進(jìn)制表示惑折,把是 1 的位對應(yīng)原數(shù)組中的數(shù)字取出來就是一個子集授账,八種情況都取出來就是所有的子集了,參見代碼如下:
1 | 2 | 3 | Subset | |
---|---|---|---|---|
0 | F | F | F | [] |
1 | F | F | T | [3] |
2 | F | T | F | [2] |
3 | F | T | T | [2, 3] |
4 | T | F | F | [1] |
5 | T | F | T | [1, 3] |
6 | T | T | F | [1, 2] |
7 | T | T | T | [1, 2, 3] |
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
int max = 1 << nums.length;
for (int i = 0; i < max; i++) {
List<Integer> list = convertIntToSet(nums, i);
lists.add(list);
}
return lists;
}
private List<Integer> convertIntToSet(int[] nums, int i) {
List<Integer> sub = new ArrayList<>();
int idx = 0;
for (int k = i; k > 0; k >>= 1) {
if ((k & 1) == 1) {
sub.add(nums[idx]);
}
idx++;
}
return sub;
}
本文參考自 [LeetCode] Subsets 子集合