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%。
这道题自己其实很是直白,思路也很直接。因此在优化过程当中,引入了一种不是特别常见的排序方式,并进行了说明。但愿尚未接触过的小伙伴们能有所收获。