又一個同學被快手掛掉了

又一個同學被快手掛掉了


今天是小浩算法 “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





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章