寶寶也能看懂的 leetcode 周賽 - 174 - 2

1338. 數組大小減半

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

解決思路

嘛,這道題就很直白,沒什麼包裝,致使小豬都不知道該寫些什麼啦。出題的小可愛,小豬討厭你!哼 >.<

因爲題目須要的是用最少的次數刪掉一半及以上的數據,那麼小豬看完以後的第一反應就是,咱們是否是應該每次都選剩下的數據中最多數量的那個刪除呢?因爲各個數據之間沒有任何聯繫,而且對於刪除數據的要求只有一條,即相同的數字,那麼這種思路實際上是能直接獲得最優解的。

相信細心的小夥伴已經發現啦,其實這就是用局部最優解累積獲得全局最優解的過程,也就是貪心算法啦。

直接方案

基於上面的思路,咱們能夠獲得下面的流程:

  1. 對原始數據進行計數統計。
  2. 按照降序排序。
  3. 對排序的結果逐漸求和,直到和大於等於原始數據長度的一半。

基於這個流程,咱們能夠實現相似下面的代碼:

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),優於傳統的基於比較和交換的排序算法。不過它也有很大的限制,須要咱們能舉出全部的可能。而且若是這個範圍太大的話,須要排序的數據量又比較小,那麼也會得不償失。

基於上面介紹的這種桶排序,咱們回到這道題目,能夠獲得以下的流程:

  1. 對原始數據進行計數統計。
  2. 基於桶排序進行排序,並記錄每種計數頻次的數據的數量。
  3. 從大到小的遍歷結果並求和,直到和大於等於原始數據長度的一半。

基於這個流程,咱們能夠實現相似下面的代碼:

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%。

總結

這道題自己其實很是直白,思路也很直接。因此在優化過程當中,引入了一種不是特別常見的排序方式,並進行了說明。但願尚未接觸過的小夥伴們能有所收穫。

相關連接

qrcode_green.jpeg

相關文章
相關標籤/搜索