Leetcode 78 - 子集
1 題目
https://leetcode-cn.com/problems/subsets/
2 題意
給定一組不含重複元素的整數陣列 nums,返回該陣列所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例:
輸入: nums = [1,2,3]
輸出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
3 解題思路
author's blog == http://www.cnblogs.com/toulanboy/
(1)子集的概念
子集,簡單來說,就是將原集合的一部分元素抽出來形成的新集合。子集包含的元素個數可以是0~n
n: 原集合的長度
(2)如何看待子集的形成
由於子集是原集合的一部分元素,那麼對於每一個原集合的元素,都有可能被抽出或者不被抽出。
所以,我們可以給每一個原集合的元素用一個bit
來標記其是否被抽出到新集合。
- 0代表沒被抽出(沒被選中)
- 1代表被抽出(被選中)
那麼,若原集合有n個元素,由於每個元素都有選或不選兩種可能,所以從排列組合的角度出發,原集合有2^n
個子集。
(3)舉例學習
那麼對於題目的樣例{1, 2, 3}
從排列組合的角度來看,其一共有3
個元素,每個元素都有選或不選的可能性,故共有2^3
,即8
個子集。
其子集和二進位制的關係如下表
子集 | 二進位制(為方便理解,部分有註釋) | 十進位制 |
---|---|---|
[] | 000(3個都不選) | 0 |
[3] | 001(只選第3個) | 1 |
[2] | 010(只選第2個) | 2 |
[2, 3] | 011(選第2、第3) | 3 |
[1] | 100 | 4 |
[1, 3] | 101 | 5 |
[1, 2] | 110 | 6 |
[1, 2, 3] | 111 | 7 |
通過分析這個例子,我們可以發現一個特性:
列舉子集的過程,實際上列舉二進位制從000
加到111
的過程,也就是從0
加到7
的過程。
該特性也可以從組合數學的角度出發分析:子集的每個元素都是選或不選,所以若原集合有n個元素,那麼n個bit組成的二進位制產生的所有情況都會被列舉到。換算成十進位制的角度,就是枚舉了0到2^n的過程。
(4)重要結論
- 子集的列舉(以原集合只有3個元素為例子),實際上是列舉每一個元素的選或不選,等同於列舉3個bit組成的二進位制的所有情況。而這個所有情況,可以通過列舉從
000
加到111
實現。 - 000加到111的過程,可以是簡單的二進位制的加法。也可以是換算成
10進位制
進行加法。
官方題解是基於十進位制加法實現的,我的程式碼是基於二進位制加法實現的,好處是二進位制加法可以處理元素個數大於64的情況。
(5)知識補充
這裡主要是談談二進位制的加法。
最直接是方式是 從個位開始加,然後不斷進位,但是每次加法都需要遍歷整個陣列。
但實際上,通過觀察二進位制的加法過程,有一個更好的方法:
具體方案:對於一個二進位制,當需要進行加1時,只需從右往左找到第1個0,將這個0變為1,將這個0後面的1變為0。
4 程式碼實現
//author's blog == http://www.cnblogs.com/toulanboy/
class Solution {
public:
//add_one() : 對二進位制進行加1
//對於一個二進位制,當需要進行加1時,只需從右往左找到第1個0,將這個0變為1,將這個0後面的1變為0。
void add_one(int * binary, int n){
for(int i=n-1; i>=0; i--){
if(binary[i] == 0){
binary[i] = 1;
break;
}
else{
binary[i] = 0;
}
}
}
vector<vector<int>> subsets(vector<int>& nums) {
int n = nums.size();
//建立長度為nums.size(),用來表示原集合的n個元素的選擇情況。(思路中涉及到的二進位制陣列)
int binary[n];
memset(binary, 0, sizeof(binary));
int times = pow(2, n);
vector<vector<int>> ans;//儲存所有子集
vector<int> sub_set; //儲存一個子集
while(times--){//一共有2^n個子集
sub_set.clear();
for(int i=0; i<n; ++i){//列舉選擇情況
if(binary[i] == 1){
sub_set.push_back(nums[i]);
}
}
ans.push_back(sub_set);
//讓二進位制從0000累加到1111
//十進位制的角度:從0加到2^n-1
add_one(binary, n);//二進位制加1
}
return ans;
}
};