1. 问题背景与题目解析
今天我们来拆解LeetCode第1481题"不同整数的最少数目"。这是一道中等难度的贪心算法题目,题目要求我们通过移除数组中的某些元素,使得剩下的数组中不同整数的数量不超过k个,同时要保证移除的元素数量最少。
题目描述如下:
给定一个整数数组arr和一个整数k。我们需要从数组中删除最少数量的元素,使得剩下的数组中不同整数的数量不超过k。
示例:
输入:arr = [5,5,4,4,3], k = 1
输出:2
解释:我们可以移除两个4,这样剩下的数组就只有5和3两个不同的整数(但题目要求不超过k=1个),所以实际上我们需要移除所有非5或非3的元素。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思路分析
2.1 问题转化与理解
这道题的核心在于如何高效地选择要移除的元素。我们需要明确几个关键点:
- 我们需要统计数组中每个数字出现的频率
- 我们需要决定保留哪些数字,移除哪些数字
- 目标是使移除的元素数量最少
正确的思路应该是:
- 首先统计每个数字的出现频率
- 然后按照频率从低到高排序
- 优先移除出现次数最少的数字,因为这样可以用最少的移除操作减少不同整数的数量
2.2 贪心算法选择
为什么贪心算法适用于这个问题?因为我们需要做出局部最优选择来达到全局最优解。具体来说:
- 每次我们都选择移除出现次数最少的数字
- 这样可以用最少的移除操作减少不同整数的数量
- 这种局部最优选择最终会导致全局最优解
这种策略之所以有效,是因为移除一个出现次数多的数字需要移除更多元素才能减少不同整数的数量,而移除出现次数少的数字则更高效。
3. 详细解题步骤
3.1 统计频率
首先,我们需要统计数组中每个数字出现的频率。这可以通过哈希表(字典)来实现:
python复制from collections import defaultdict
def findLeastNumOfUniqueInts(arr, k):
freq = defaultdict(int)
for num in arr:
freq[num] += 1
3.2 频率排序
接下来,我们需要将这些频率按从小到大的顺序排序:
python复制sorted_freq = sorted(freq.values())
3.3 贪心移除
然后,我们开始贪心地移除出现次数最少的数字:
python复制removals = 0
unique_count = len(sorted_freq)
for count in sorted_freq:
if k >= count:
k -= count
removals += 1
else:
break
return unique_count - removals
3.4 完整代码
将以上步骤组合起来,完整的Python解决方案如下:
python复制from collections import defaultdict
def findLeastNumOfUniqueInts(arr, k):
freq = defaultdict(int)
for num in arr:
freq[num] += 1
sorted_freq = sorted(freq.values())
removals = 0
unique_count = len(sorted_freq)
for count in sorted_freq:
if k >= count:
k -= count
removals += 1
else:
break
return unique_count - removals
4. 复杂度分析
让我们分析一下这个算法的时间和空间复杂度:
-
时间复杂度:
- 统计频率:O(n),需要遍历整个数组
- 排序频率:O(m log m),其中m是不同整数的数量
- 贪心移除:O(m)
- 总体:O(n + m log m)
-
空间复杂度:
- 哈希表存储频率:O(m)
- 排序需要额外空间:O(m)
- 总体:O(m)
在大多数情况下,m ≤ n,所以可以简化为O(n log n)时间复杂度和O(n)空间复杂度。
5. 边界条件与测试用例
5.1 常见边界情况
在解决这个问题时,我们需要考虑以下几种边界情况:
- 空数组:arr = [], k = 0
- k = 0:不能移除任何元素
- k ≥ 数组长度:可以移除所有元素
- 所有元素相同的情况
- 每个元素都不同的情况
5.2 测试用例示例
python复制# 测试用例1:示例情况
assert findLeastNumOfUniqueInts([5,5,4,4,3], 1) == 1
# 测试用例2:k=0
assert findLeastNumOfUniqueInts([1,2,3], 0) == 3
# 测试用例3:k大于数组长度
assert findLeastNumOfUniqueInts([1,1,2,2], 5) == 0
# 测试用例4:所有元素相同
assert findLeastNumOfUniqueInts([7,7,7,7], 2) == 1
6. 算法优化思考
虽然上述解法已经相当高效,但我们还可以考虑一些优化方向:
- 使用堆数据结构:我们可以使用最小堆来存储频率,这样不需要显式排序
- 早期终止:如果在移除过程中k已经减到0,可以提前终止循环
- 空间优化:如果数组中的数字范围有限,可以使用数组代替哈希表
使用堆的优化版本:
python复制import heapq
from collections import defaultdict
def findLeastNumOfUniqueInts(arr, k):
freq = defaultdict(int)
for num in arr:
freq[num] += 1
min_heap = list(freq.values())
heapq.heapify(min_heap)
while min_heap and k > 0:
count = heapq.heappop(min_heap)
if k >= count:
k -= count
else:
heapq.heappush(min_heap, count - k)
k = 0
return len(min_heap)
7. 类似题目推荐
为了加深对这类问题的理解,我推荐练习以下类似题目:
- LeetCode 347. 前K个高频元素
- LeetCode 451. 根据字符出现频率排序
- LeetCode 692. 前K个高频单词
- LeetCode 1054. 距离相等的条形码
- LeetCode 767. 重构字符串
这些题目都涉及到频率统计和贪心算法的应用,可以帮助巩固相关的解题技巧。
8. 实际应用场景
这类问题在实际开发中有多种应用场景:
- 数据压缩:选择性地移除一些数据以减少存储空间
- 缓存淘汰策略:决定哪些数据应该从缓存中移除
- 资源分配:在有限资源下选择保留哪些项目
- 特征选择:在机器学习中选择最重要的特征
理解这类算法可以帮助我们在面对类似的实际问题时快速找到解决方案。
9. 常见错误与调试技巧
在解决这个问题时,初学者常犯的一些错误包括:
- 错误理解题意:以为是要保留k个元素,而不是k种不同的元素
- 排序方向错误:应该按频率从小到大排序,而不是从大到小
- 忽略k=0的情况:需要特殊处理
- 移除逻辑错误:应该累计移除的数量,而不是直接减
调试技巧:
- 打印中间变量:特别是在频率统计和排序后
- 使用小测试用例:手动验证算法的正确性
- 逐步执行:在IDE中逐步执行代码,观察变量变化
10. 个人解题心得
在解决这个问题时,我最初尝试了直接统计频率后随机移除元素的方法,但发现这样无法保证移除数量最少。后来意识到应该优先移除出现次数最少的数字,这才是贪心算法的精髓。
一个重要的启示是:在解决涉及"最少"或"最多"的问题时,贪心算法往往是一个有效的选择,关键在于找到正确的贪心策略。
另一个心得是:在LeetCode解题时,一定要仔细阅读题目描述,确保完全理解题意。这道题容易误解为"保留k个元素",而实际上是"保留不超过k种不同的元素",这种细微差别会导致完全不同的解法。
