LeetCode 1481中等贪心/哈希
不同整数的最少数目 图解题解
这道题到底在问什么
给定整数数组 arr 和整数 k,删除恰好 k 个元素后,返回剩下不同整数的最少数量。删一个数字的某一类,必须把这类全删完,种类才会减一。
- 输入
- arr=[5,5,4], k=1
- 输出
- 1 (删掉 4,只剩 5)
- 输入
- arr=[4,3,1,1,3,3,2], k=3
- 输出
- 2 (优先删低频:删 4 删 2,剩 1 和 3 两种)
最优解:一步一步想明白
- 3记住「先灭最弱的种类」:同样花预算 k,干掉出现次数少的数字最划算,种类数掉得最快。
- 4第一拍:扫到第 0 个元素 4(紫色)。先看频次表里有没有它:没有,待会儿新建一行。
- 5第二拍:频次表新增一行 4:1(高亮行)。
- 6第一拍:扫到第 1 个元素 3(紫色)。先看频次表里有没有它:没有,待会儿新建一行。
- 7第二拍:频次表新增一行 3:1(高亮行)。
- 8第一拍:扫到第 2 个元素 1(紫色)。先看频次表里有没有它:没有,待会儿新建一行。
- 9第二拍:频次表新增一行 1:1(高亮行)。
- 10第一拍:扫到第 3 个元素 1(紫色)。先看频次表里有没有它:已经有了,待会儿次数 +1。
- 11第二拍:把 1 的次数更新到 2(高亮行)。
- 12第一拍:扫到第 4 个元素 3(紫色)。先看频次表里有没有它:已经有了,待会儿次数 +1。
- 13第二拍:把 3 的次数更新到 2(高亮行)。
- 14第一拍:扫到第 5 个元素 3(紫色)。先看频次表里有没有它:已经有了,待会儿次数 +1。
- 15第二拍:把 3 的次数更新到 3(高亮行)。
- 16第一拍:扫到第 6 个元素 2(紫色)。先看频次表里有没有它:没有,待会儿新建一行。
- 17第二拍:频次表新增一行 2:1(高亮行)。
- 18全部扫完,频次表成型:4→1、3→3、1→2、2→1,一共 4 种数字。接下来只关心这些「次数」,元素本身不再重要。
- 19现在舞台上的每个格子是一个「种类的出现次数」,已从小到大排好:[1, 1, 2, 3]。最左边是最容易被整类删掉的种类。
- 20看第 0 个种类:它出现 1 次。手里还有 k=3 次删除额度,够不够把这 1 个全删光?
- 21k ≥ 1,把这一整类删干净:预算扣掉 1 变成 k=2,不同种类数减到 3。这一格记为已清空(绿色一下,随后灰掉)。
- 22看第 1 个种类:它出现 1 次。手里还有 k=2 次删除额度,够不够把这 1 个全删光?
- 23k ≥ 1,把这一整类删干净:预算扣掉 1 变成 k=1,不同种类数减到 2。这一格记为已清空(绿色一下,随后灰掉)。
- 24看第 2 个种类:它出现 2 次。手里还有 k=1 次删除额度,够不够把这 2 个全删光?
- 25k=1 已经小于 2,删不动这一整类了(红色)。剩下的 1 个额度只能从某一类里删掉一部分,删不光就不会让种类减少,所以计数到此为止。剩下的种类数就是答案。
- 26收尾:左边 2 个低频种类被整类删掉(灰),耗掉 2 个额度;还剩的 1 个额度从某个高频类里删掉一部分(删不光、种类不减),正好凑满删 3 个。绿色这 2 个高频种类还在。答案 = 2。
⚠️ 容易写错的地方
✗ 错:从高频开始删
✓ 对:从低频开始删
同样的预算,删低频种类才能让种类数掉得最快
✗ 错:把一类删了一半也算减少种类
✓ 对:必须整类删完种类才减一
只要还剩一个,这种数字就仍存在,种类不变
✗ 错:忘了 k 可能为 0 或一个都删不动
✓ 对:k 不足以删任何一整类时直接返回当前种类数
半删无意义,剩余种类就是答案
完整代码(Python / C++ / Java)
Python
from typing import List
from collections import Counter
class Solution:
def findLeastNumOfUniqueInts(self, arr: List[int], k: int) -> int:
freq = sorted(Counter(arr).values())
remain = len(freq)
for c in freq:
if k >= c:
k -= c
remain -= 1
else:
break
return remainC++
#include <algorithm>
#include <unordered_map>
#include <vector>
using namespace std;
class Solution {
public:
int findLeastNumOfUniqueInts(vector<int>& arr, int k) {
unordered_map<int,int> count;
for (int x : arr) count[x]++;
vector<int> freq;
for (auto &p : count) freq.push_back(p.second);
sort(freq.begin(), freq.end());
int remain = freq.size();
for (int c : freq) {
if (k >= c) { k -= c; remain--; }
else break;
}
return remain;
}
};Java
import java.util.*;
class Solution {
public int findLeastNumOfUniqueInts(int[] arr, int k) {
Map<Integer, Integer> count = new HashMap<>();
for (int x : arr) count.put(x, count.getOrDefault(x, 0) + 1);
List<Integer> freq = new ArrayList<>(count.values());
Collections.sort(freq);
int remain = freq.size();
for (int c : freq) {
if (k >= c) { k -= c; remain--; }
else break;
}
return remain;
}
}复杂度
时间
O(n log n)
统计 O(n),对至多 n 个频次排序 O(n log n)
空间
O(n)
哈希表与频次数组最多存 n 个不同数字
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 不同整数的最少数目 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能不能不排序,做到 O(n)?+
可以。频次值的范围在 1..n 之间,用计数排序/桶:开一个长度 n+1 的桶 bucket[c] = 有多少个种类出现 c 次,然后从频次 1 往上遍历,能整类删就删。整体 O(n),省掉排序的 log。
如果要求删除后不同整数「最多」,思路要怎么变?+
反过来贪心:优先删「高频」种类里多余的个数(每类只删到剩 1 个,尽量不让任何种类清零),或干脆只删某一类的若干个,使清零的种类数最少。本题是求最少种类,所以是先灭低频。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 不同整数的最少数目 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。