Hi 你們好,我是張小豬。歡迎來到『寶寶也能看懂』系列之 leetcode 周賽題解。git
這裏是第 174 期的第 2 題,也是題目列表中的第 1338 題 -- 『數組大小減半』github
給你一個整數數組 arr
。你能夠從中選出一個整數集合,並刪除這些整數在數組中的每次出現。算法
返回 至少 能刪除數組中的一半整數的整數集合的最小大小。shell
示例 1:segmentfault
輸入:arr = [3,3,3,3,5,5,5,2,2,7] 輸出:2 解釋:選擇 {3,7} 使得結果數組爲 [5,5,5,2,2]、長度爲 5(原數組長度的一半)。 大小爲 2 的可行集合有 {3,5},{3,2},{5,2}。 選擇 {2,7} 是不可行的,它的結果數組爲 [3,3,3,3,5,5,5],新數組長度大於原數組的二分之一。
示例 2:數組
輸入:arr = [7,7,7,7,7,7] 輸出:1 解釋:咱們只能選擇集合 {7},結果數組爲空。
示例 3:優化
輸入:arr = [1,9] 輸出:1
示例 4:spa
輸入:arr = [1000,1000,3,7] 輸出:1
示例 5:code
輸入:arr = [1,2,3,4,5,6,7,8,9,10] 輸出:5
提示:blog
1 <= arr.length <= 10^5
arr.length
爲偶數1 <= arr[i] <= 10^5
MEDIUM
嘛,這道題就很直白,沒什麼包裝,致使小豬都不知道該寫些什麼啦。出題的小可愛,小豬討厭你!哼 >.<
因爲題目須要的是用最少的次數刪掉一半及以上的數據,那麼小豬看完以後的第一反應就是,咱們是否是應該每次都選剩下的數據中最多數量的那個刪除呢?因爲各個數據之間沒有任何聯繫,而且對於刪除數據的要求只有一條,即相同的數字,那麼這種思路實際上是能直接獲得最優解的。
相信細心的小夥伴已經發現啦,其實這就是用局部最優解累積獲得全局最優解的過程,也就是貪心算法啦。
基於上面的思路,咱們能夠獲得下面的流程:
基於這個流程,咱們能夠實現相似下面的代碼:
const minSetSize = arr => { const LEN = arr.length; if (LEN < 3) return 1; const max = Math.max(...arr); const freq = new Uint16Array(max + 1); for (const val of arr) ++freq[val]; let step = 0; let sum = 0; freq.sort((a, b) => b - a); for (const val of freq) { sum += val; ++step; if (sum >= LEN / 2) return step; } };
這段代碼跑了 96ms,暫時 beats 100%。
上面的代碼咱們對統計計數進行了傳統排序,複雜度就達到了 O(nlogn)。咱們是否有方法下降這個複雜度呢?
這裏介紹一種不那麼傳統的排序方式 -- 桶排序。咱們先來看一個栗子:
咱們如今假設有 2000 個學生,他們剛進行完一次考試,每一個人考試成績的範圍是 [1, 100]
。如今咱們須要把他們這一次考試的成績按照升序進行排序。
因爲他們的考試成績的範圍並不大,咱們能夠先假設如今有 100 個桶,正好覆蓋了每個成績的可能。而後咱們把 1 分的試卷放進 1 號桶,把 2 分的試卷放進 2 號桶。以此類推,直到全部的試卷都放進了這 100 個桶子。不知道小夥伴們有沒有發現,這時候咱們其實就已經完成了對這 2000 份試卷的排序。咱們只須要從低到高的查看每個桶子裏試卷的數量便可。
這種排序方式有個很是大的優點,即它的時間複雜度只有 O(n),優於傳統的基於比較和交換的排序算法。不過它也有很大的限制,須要咱們能舉出全部的可能。而且若是這個範圍太大的話,須要排序的數據量又比較小,那麼也會得不償失。
基於上面介紹的這種桶排序,咱們回到這道題目,能夠獲得以下的流程:
基於這個流程,咱們能夠實現相似下面的代碼:
const minSetSize = arr => { const LEN = arr.length; if (LEN < 3) return 1; const max = Math.max(...arr); const freq = new Uint16Array(max + 1); let maxFreq = 0; for (const val of arr) ++freq[val] > maxFreq && (maxFreq = freq[val]); const freqBased = new Uint32Array(maxFreq + 1); for (const val of freq) ++freqBased[val]; let step = 0; let sum = 0; for (let i = maxFreq; i >= 1; --i) { while (freqBased[i]--) { sum += i; ++step; if (sum >= LEN / 2) return step; } } };
這段代碼跑了 80ms,取代了上面的代碼暫時 beats 100%。
這道題自己其實很是直白,思路也很直接。因此在優化過程當中,引入了一種不是特別常見的排序方式,並進行了說明。但願尚未接觸過的小夥伴們能有所收穫。