今天是小浩算法 “365刷題計劃” 第105天。這是昨天一個同學面試快手被問到的算法題,很不幸的是他被掛掉了。徵得對方同意後,拿出來分享給大家~
(如果要進入算法交流羣的,
關注後回覆進羣就可以了)
01
PART
子集
子集:如果集合A的任意一個元素都是集合B的元素,那麼集合A稱爲集合B的子集。
第48題:給定一組不含重複元素的整數數組 nums,返回該數組所有可能的子集(冪集)。
說明:解集不能包含重複的子集
示例:
輸入: nums = [1,2,3]
輸出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
題目本身沒有太多需要補充的,初中數學知識:
02
PART
題解分析(吊)
上一個很厲害的題解。
首先我們可以證明一下 N 個元素的子集個數有 2^N 個:
可以類比爲 N 個不同的小球,一次拿出若干個小球(可以不拿),對於每一個球都可以選擇拿或者不拿,共有 N 個球,總共判斷 N 次,產生了 2^N 個子集。比如:123,共有下面 8 個子集:
然後考慮解題思路,暫且不談回溯,我們其實可以用二進制來模擬每個元素是否選中的狀態。又因爲我們已知了對於 N 個元素共有 2^N 個子集,所以我們直接遍歷 2^N 個元素。
1class Solution {
2 public List<List<Integer>> subsets(int[] nums) {
3 //存放所有子集
4 List<List<Integer>> res = new ArrayList<>();
5 //子集總數共有 2^N 個
6 int length = 1 << nums.length;
7 //遍歷所有的子集
8 for (int i = 0; i < length; i++) {
9 List<Integer> sub = new ArrayList<>();
10 //TODO : 找到對應的子集元素
11 }
12 return res;
13 }
14}
但是我們並不知道具體的子集元素。那如何找到對應的子集元素呢?對於 2^N 個 N 位的二進制數,我們可以通過從後往前的第 j 個二進制位的 0 和 1 來表示是否放入子集集合。
1for (int j = 0; j < nums.length; j++) {
2 if (((i >> j) & 1) == 1) sub.add(nums[j]);
3}
綜合一下代碼:
1class Solution {
2 public List<List<Integer>> subsets(int[] nums) {
3 //存放所有子集
4 List<List<Integer>> res = new ArrayList<>();
5 //子集總數公有 2^N 個
6 int length = 1 << nums.length;
7 //遍歷所有的子集
8 for (int i = 0; i < length; i++) {
9 List<Integer> sub = new ArrayList<>();
10 for (int j = 0; j < nums.length; j++) {
11 if (((i >> j) & 1) == 1) sub.add(nums[j]);
12 }
13 res.add(sub);
14 }
15 return res;
16 }
17}
爲幫助大家理解,假設 nums 爲 [1,2,3],res 的存儲過程爲:
大家可以仔細體會一下這個題解。
03
PART
題解分析(普通)
當然,上面的題解並不是凡人可以直接想到的。所以我們這裏還是給出一種更爲通用的題解~
集合中所有元素的選/不選,其實構成了一個滿二叉樹。左子樹選,右子樹不選。自然,那從根節點到所有葉子節點的路徑,就構成了所有的子集。
(旋轉90°)
那這種解法其實就好理解很多了:
1class Solution {
2
3 List<List<Integer>> res;
4
5 public List<List<Integer>> subsets(int[] nums) {
6 res = new ArrayList<>();
7 List<Integer> list = new ArrayList<>();
8 dfs(nums, 0, list);
9 return res;
10 }
11
12 private void dfs(int[] nums, int start, List<Integer> list) {
13 for (int i = start; i < nums.length; i++) {
14 list.add(nums[i]);
15 dfs(nums, i + 1, list);
16 list.remove(list.size() - 1);
17 }
18 res.add(new ArrayList<>(list));
19 }
20
21}
如果對這種解法也不理解,可以看下我之前的二叉樹系列:
總之,這道題目其實還是有一定難度的,難點主要包括:
-
數學知識的混淆,忘記考慮空集等情況。
-
和全排列問題混淆,把 2^N 當做 N!處理。
- 遞歸與回溯細節掌握不紮實。
但並不是不可以攻克,建議大家下去自行練習一番~
加油,奧利給!
如果你也想加入我們每日刷題
掃碼,回覆【進羣】就可以啦。
推薦幾篇必看文章:
漫畫:小白爲了面試如何刷題?(嘔心瀝血算法指導篇)
漫畫:嘔心泣血算法指導篇(真正的乾貨,怒懟那些說算法沒用的人)
動態規劃入門看這篇就夠了,萬字長文!
萬字長文!位運算面試看這篇就夠了!
彩蛋:
1//py
2class Solution:
3 def subsets(self, nums: List[int]) -> List[List[int]]:
4 arr = [[]]
5 for i in nums:
6 arr+=[j+[i] for j in arr]
7 return arr