LeetCode 1207. 独一无二的出现次数
题目描述

题意分析
统计数组里每一种不同数值各出现多少次,再判断这些出现次数是否两两不同。只要两个不同的值具有相同次数,就返回
false;所有频次都不重复才返回true。元素值本身可以重复,也可以为负数;要去重比较的是最终次数,而不是原数组中的值。数组中只有一种数值时,无论它出现多少次,都不存在两个值频次相同的冲突。
解法:先按值计数,再对次数判重
核心思路
[!blue]
先用哈希表
count把相同元素合并计数。第一阶段的键是元素值,值是这个元素在整个数组中的次数;必须完成全部统计后再比较,扫描过程中的临时次数还会继续变化,不能提前据此判定冲突。再用集合
times保存已经检查过的频次。逐个读取计数表的值,如果这个次数尚未见过就加入集合;如果已经存在,就找到了两个不同数值的相同最终次数,可以立即返回false。第一张表对每个数值只保留一个条目,所以第二阶段每次访问必然来自不同的数值。集合因此正好检测题目要排除的频次重复,而不会把同一个数值的多次出现当成多个冲突项。全部条目都能成功加入时,次数就两两不同。
解题步骤
- 扫描原数组,按数值累加次数。
- 创建空的频次集合,遍历计数表中每个不同值对应的最终次数。
- 某个次数已经出现时立即返回
false,否则登记它。- 所有频次都没有重复,返回
true。
代码实现
class Solution {
public boolean uniqueOccurrences(int[] arr) {
Map<Integer, Integer> count = new HashMap<>();
for (int num : arr) {
// 第一阶段:按值计数,负数也能直接当键,不需要下标偏移。
count.put(num, count.getOrDefault(num, 0) + 1);
}
Set<Integer> times = new HashSet<>();
for (int c : count.values()) {
// 遍历的是值(次数)而不是键;add 返回 false 说明这个次数已出现过。
if (!times.add(c)) {
return false;
}
}
return true;
}
}
func uniqueOccurrences(arr []int) bool {
count := make(map[int]int)
for _, num := range arr {
// 第一阶段:map 的零值就是 0,可以直接自增。
count[num]++
}
times := make(map[int]bool)
for _, c := range count {
// 先查再写:已经见过这个次数说明有两个值的出现次数相同。
if times[c] {
return false
}
times[c] = true
}
return true
}
复杂度分析
- 时间复杂度:期望 $O(n)$,统计扫描
n个元素,再检查不超过n个不同值的频次。- 空间复杂度:$O(u)$,
u为不同元素值的数量,次数表与频次集合各至多保存u项。
关键点总结
[!green]
- 第一层按元素值聚合,第二层按最终次数判重,两个键的含义不同。
- 先完整统计再判重,避免把还会增长的临时次数当作结论。
- Java 集合添加失败或 Go 集合已包含该次数,都直接表示找到了重复频次。
易错点总结
[!yellow]
- 直接判断原数组有没有重复元素,回答了与题目不同的问题。
- 遍历计数表的键来去重,键本来就不同,无法检查频次冲突。
- 还未统计完整数组就因临时频次相等返回失败,后续出现可能会让最终次数不同。
- 把同一个数值的每次出现都放入频次集合,会重复检查同一条统计记录。
- 直接用原值索引没有处理负数范围的数组,可能越界;哈希键可以直接保存负数。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 1657. 确定两个字符串是否接近 | 中等 | 同样把字符频次作为另一层数据,本题要求频次互异,原题比较两串频次多重集合。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!