LeetCode-Go 题解:1207. Unique Number of Occurrences 唯一出现次数判断
【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go
导读
本篇围绕 LeetCode 第 1207 题「Unique Number of Occurrences(唯一出现次数)」展开,以 LeetCode-Go 仓库中该题目的 README 为骨架,结合其 Go 实现与测试用例进行源码级剖析。读完本文,你将掌握"统计频次 + 判重"这一经典双哈希表套路,理解本仓库 1207 题的官方解法 的时间/空间复杂度,并能通过仓库自带的测试代码独立验证结论。
题目描述
Given an array of integers
arr, write a function that returnstrueif and only if the number of occurrences of each value in the array is unique.
给定一个整数数组arr,统计数组中每个数值出现的次数;只有当数组中每个数值的出现次数都互不相同时,函数才返回true,否则返回false。
示例
示例 1
Input: arr = [1,2,2,1,1,3] Output: true解释:数值 1 出现 3 次,数值 2 出现 2 次,数值 3 出现 1 次。三个出现次数(3、2、1)互不相同,因此返回true。
示例 2
Input: arr = [1,2] Output: false解释:数值 1 和数值 2 各出现 1 次,出现次数重复(都为 1),因此返回false。
示例 3
Input: arr = [-3,0,1,-3,1,1,1,-3,10,0] Output: true解释:数组长度为 10,各数值出现次数为:-3 出现 3 次、0 出现 2 次、1 出现 4 次、10 出现 1 次,互不相同,返回true。
数据约束
1 <= arr.length <= 1000-1000 <= arr[i] <= 1000
约束意味着:数组非空、长度不超过 1000;元素取值落在[-1000, 1000]的闭区间内。由于每个数值最多出现arr.length次,频次的最大可能值也是 1000,因此频次本身可以用普通int安全存储,不存在溢出问题。
解题思路
这是一道典型的哈希表计数入门题,核心思路分两步:
- 统计频次:遍历数组,用一张哈希表(
map)记录每个数值出现的次数; - 频次判重:遍历频次表,用另一张哈希表记录"哪些频次已经出现过",一旦发现某个频次重复出现,立即返回
false;全部不重复则返回true。
之所以需要两张哈希表,是因为问题同时要求「值 → 频次」和「频次是否唯一」两类信息:第一张表完成聚合统计,第二张表完成唯一性校验,职责分离、逻辑清晰。
仓库源码实现解析
仓库中 1207 题的 Go 实现 完整代码如下:
package leetcode func uniqueOccurrences(arr []int) bool { freq, m := map[int]int{}, map[int]bool{} for _, v := range arr { freq[v]++ } for _, v := range freq { if _, ok := m[v]; !ok { m[v] = true } else { return false } } return true }逐行解读
freq, m := map[int]int{}, map[int]bool{}:同时声明两张哈希表。freq的键是数组元素的值、值是出现次数;m的键是频次、值是布尔标记,充当"频次集合"。- 第一个
for range循环完成频次统计:freq[v]++对map中不存在的键自动初始化为零值0再加一,等价于先取值、加一、再写回,是 Go 语言map计数的惯用写法。 - 第二个
for range循环遍历freq的所有键值对(v为频次)。采用if _, ok := m[v]; !ok的「逗号 ok」惯用法判断频次是否已存在:- 不存在(
!ok):标记为已出现; - 已存在(
ok):说明有两个不同的数值拥有相同出现次数,直接return false。
- 不存在(
- 循环正常结束意味着所有频次唯一,返回
true。
复杂度分析
- 时间复杂度:
O(n),其中n = arr.length。第一遍遍历数组统计频次为O(n),第二遍遍历频次表最多为O(n)(去重后键的数量不超过 n),整体线性。 - 空间复杂度:
O(n)。两张哈希表在最坏情况下(每个元素都不同)各需存储 n 个条目。
实现细节上的两个可优化点
从代码结构看,该实现有以下两个值得注意的设计取舍:
- 提前返回:第二遍遍历在发现重复频次时立即
return false,无需处理完整个频次表,在"大概率不满足条件"的输入上可以提前结束; - 判重写法的简化空间:其实判重逻辑可进一步缩写为
if m[v] { return false }; m[v] = true。仓库保留显式的_, ok写法,语义更直白,也更贴近 Go 官方 Code Review 注释中推崇的可读性风格(仓库 README 明确声明代码风格遵循 Google Golang Style Guide)。
测试用例与验证
仓库为每题配套了表驱动风格的测试文件,1207 题的测试代码 结构如下:
package leetcode import ( "fmt" "testing" ) type question1207 struct { para1207 ans1207 } // para 是参数 // one 代表第一个参数 type para1207 struct { arr []int } // ans 是答案 // one 代表第一个答案 type ans1207 struct { one bool } func Test_Problem1207(t *testing.T) { qs := []question1207{ { para1207{[]int{1, 2, 2, 1, 1, 3}}, ans1207{true}, }, { para1207{[]int{1, 2}}, ans1207{false}, }, { para1207{[]int{-3, 0, 1, -3, 1, 1, 1, -3, 10, 0}}, ans1207{true}, }, } fmt.Printf("------------------------Leetcode Problem 1207------------------------\n") for _, q := range qs { _, p := q.ans1207, q.para1207 fmt.Printf("【input】:%v 【output】:%v\n", p, uniqueOccurrences(p.arr)) } fmt.Printf("\n\n\n") }该测试覆盖了题目给出的全部三个示例:true的正例(示例 1、示例 3,含负数和 0 的场景)与false的反例(示例 2)。测试采用question1207结构体将输入参数para1207与期望答案ans1207捆绑,是 LeetCode-Go 仓库统一使用的表驱动测试约定。
仓库通过 gotest.sh 执行全量测试与覆盖率采集:
go test -covermode=atomic -coverprofile=coverage.txt ./leetcode/...在leetcode/目录下运行该命令即可连同本题一起执行测试,生成的coverage.txt可用于覆盖率统计(仓库项目描述中声明整体覆盖率为 100%)。若只想单独验证本题,可运行:
go test -v -run Test_Problem1207 ./leetcode/1207.Unique-Number-of-Occurrences/仓库使用 Go 1.19(见 go.mod),并提供了structures、template等本地模块的replace指令,属于单模块 + 本地子模块的组织方式,直接在本仓库根目录执行go test即可完成验证。
同类解法的横向对比
除仓库采用的"双哈希表"方案外,本题还有几种常见的等价实现,理解它们有助于加深对判重问题的认识。
方案一:频次集合长度比较
统计完频次后,将所有频次放入一个set,比较set的大小与频次总数是否相等:
func uniqueOccurrences(arr []int) bool { freq := map[int]int{} for _, v := range arr { freq[v]++ } seen := map[int]bool{} for _, c := range freq { seen[c] = true } return len(seen) == len(freq) }其本质与仓库解法完全一致:len(seen) == len(freq)成立当且仅当频次无重复。该写法更"函数式",但无法提前返回,需要完整遍历两轮;仓库实现则利用提前返回在多数场景下更快。
方案二:排序后相邻比较
将频次收集到切片后排序,再检查相邻元素是否相等:
import "sort" func uniqueOccurrences(arr []int) bool { freq := map[int]int{} for _, v := range arr { freq[v]++ } cs := make([]int, 0, len(freq)) for _, c := range freq { cs = append(cs, c) } sort.Ints(cs) for i := 1; i < len(cs); i++ { if cs[i] == cs[i-1] { return false } } return true }该方案时间复杂度退化为O(n log n),空间复杂度仍为O(n)。在n <= 1000的约束下性能差异可忽略,但哈希表方案在原理上更优,这也是仓库选择双哈希表的原因。
方案三:结合数据约束的定长数组优化
注意到约束-1000 <= arr[i] <= 1000,数组元素值域只有 2001 种可能,因此可用定长数组替代map统计频次(元素值 +1000 映射到下标 0~2000),再用另一个定长数组标记频次是否出现过。该方案将哈希的常数开销替换为连续内存访问,且空间复杂度可视为O(2001)的常数级,在极端追求性能的场景(如竞赛)下是常见优化;但可读性略逊,且当约束放宽时需同步调整数组大小。
总结
LeetCode 1207「Unique Number of Occurrences」是一道简洁而典型的哈希表应用题:先以map完成"值 → 频次"的聚合,再以第二张map完成"频次唯一性"校验,整体时间复杂度O(n)、空间复杂度O(n)。仓库实现通过"逗号 ok"惯用法与提前返回保持了代码的清晰与高效,配套测试覆盖了题目的全部官方示例,可直接运行 gotest.sh 验证。掌握这道题的双哈希表思想,对后续处理「频次统计 + 去重判重」类问题(如 451. Sort Characters By Frequency、347. Top K Frequent Elements)具有直接的迁移价值。
参考资料
- 题目官方描述与示例(leetcode.com)
- 仓库 README(LeetCode-Go 总览)
- 1207 题 Go 源码
- 1207 题测试源码
- 1207 题解题文档(本仓库 README)
- 覆盖率测试脚本
【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考