- 科学计算
【免费下载链接】codeforces-go
算法竞赛模板库 by 灵茶山艾府 💭💡🎈
本文以 LeetCode 第 178 场双周赛第一题 first-unique-even-element 的官方题解为骨架,结合开源算法模板库 codeforces-go 中该题的 Go 实现、测试用例与测试驱动,系统讲解"两趟遍历 + 哈希计数"这一基础而重要的计数范式。读完本文,你将掌握该题在 Python/Java/C++/Go 四种语言下的标准写法、复杂度推导,以及 codeforces-go 仓库如何用RunLeetCodeFuncWithFile驱动题解完成自动化评测。
一、题目背景与题意
该题出自 LeetCode 第 178 场双周赛 A 题,题目链接与函数签名可参见 a_test.go 中的注释。核心诉求可以概括为:
给定整数数组
nums,返回第一个既是偶数、又在数组中恰好出现一次的元素;若不存在这样的元素,返回-1。
从仓库中的 a.txt 可以看到两个典型的评测样例:
[3,4,2,5,4,6] 2 [4,4] -1- 样例一:
4出现了两次(索引 1、4),不满足"恰好出现一次";2是偶数且只出现一次,且是满足条件的第一个元素,故答案为2。 - 样例二:
4出现两次,没有满足条件的元素,返回-1。
题目有两个并列的筛选条件:数值为偶数(x % 2 == 0)与出现次数恰为 1。两个条件缺一不可,这是整个解法的核心约束。
二、核心思路:两趟遍历 + 哈希计数
官方题解(见 README.md)给出的思路非常朴素且高效:
- 第一趟遍历:遍历
nums,用哈希表(也可以改用数组/计数桶)统计每个数的出现次数。一个可选的优化是:只对偶数x计数,因为奇数元素永远不可能是答案,提前过滤可以节省哈希表的键空间。 - 第二趟遍历:再次按原始顺序遍历
nums,检查nums[i]是否满足nums[i] % 2 == 0且cnt[nums[i]] == 1,一旦命中立即返回nums[i]。 - 兜底逻辑:遍历结束仍未命中,返回
-1。
为什么"第一趟"与"第二趟"缺一不可?
- 单趟遍历无法判定"恰好出现一次"——必须等完整统计完成后,才知道某个数在全数组中的出现次数;
- 第二趟必须按原始顺序扫描,才能保证返回的是"第一个"满足条件的元素,而不是任意一个;
- 先统计后查询的次序,保证了结果与元素在数组中的相对顺序严格一致。
三、四语言标准实现
以下四段实现完整继承自官方题解 README.md,可直接作为各语言下的标准答案。
Python3(哈希表Counter)
class Solution: def firstUniqueEven(self, nums: List[int]) -> int: cnt = Counter(nums) for x in nums: if x % 2 == 0 and cnt[x] == 1: return x return -1Java(HashMap+merge)
class Solution { public int firstUniqueEven(int[] nums) { Map<Integer, Integer> cnt = new HashMap<>(); for (int x : nums) { if (x % 2 == 0) { cnt.merge(x, 1, Integer::sum); } } for (int x : nums) { if (x % 2 == 0 && cnt.get(x) == 1) { return x; } } return -1; } }C++(unordered_map)
class Solution { public: int firstUniqueEven(vector<int>& nums) { unordered_map<int, int> cnt; for (int x : nums) { if (x % 2 == 0) { cnt[x]++; } } for (int x : nums) { if (x % 2 == 0 && cnt[x] == 1) { return x; } } return -1; } };Go(原生map)
func firstUniqueEven(nums []int) int { cnt := map[int]int{} for _, x := range nums { if x%2 == 0 { cnt[x]++ } } for _, x := range nums { if x%2 == 0 && cnt[x] == 1 { return x } } return -1 }四段实现的关键差异点:
| 语言 | 计数容器 | 计数写入方式 | 说明 |
|---|---|---|---|
| Python3 | collections.Counter | 一行完成统计 | Counter(nums)对全部元素计数,未做偶数过滤 |
| Java | HashMap<Integer,Integer> | merge(x, 1, Integer::sum) | 仅统计偶数,写法最紧凑 |
| C++ | unordered_map<int,int> | cnt[x]++ | 仅统计偶数,注意[]会自动插入默认值 0 |
| Go | map[int]int | cnt[x]++ | 仅统计偶数,与仓库源码 a.go 完全一致 |
小提示:Java 的
HashMap与 Go 的map都只对偶数做计数(第一趟里的if x%2 == 0过滤),而 Python 的Counter统计全部元素。前者节省空间,后者代码更短,两者在复杂度量级上一致。
四、复杂度分析
官方题解(README.md)给出的结论:
- 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是
nums的长度。两趟遍历各消耗 $\mathcal{O}(n)$,哈希表的插入与查询期望均为 $\mathcal{O}(1)$,故整体为线性时间。 - 空间复杂度:$\mathcal{O}(n)$,最坏情况下(所有元素均为偶数且互不相同)哈希表需要存储 $n$ 个键。
若在 Python 中使用Counter(nums),则第一趟对全部 $n$ 个元素计数;其余语言实现只对偶数计数,实际键数量最多为偶数个数,但仍属于 $\mathcal{O}(n)$ 量级。
值得补充的一点:如果题目将数值范围限定得很小(例如 $|x| \le 10^5$),可以把哈希表替换为计数数组/桶,空间退化为 $\mathcal{O}(V)$($V$ 为值域大小),常数更小、无哈希冲突,这也是题解中"用哈希表(或者数组)"这一括注的用意所在。
五、codeforces-go 仓库中的源码级佐证
该题在仓库中的落位是 leetcode/biweekly/178/a/,共三个文件:题解 a.go、测试驱动 a_test.go、用例数据 a.txt。
5.1 题解实现与题解文档完全对齐
a.go 中的firstUniqueEven与 README 的 Go 版本逐字一致:先建map[int]int仅统计偶数频次,再按原序查找第一个频次为 1 的偶数,无果返回-1。文件头部还保留了出题/讲解者的 B 站空间注释,方便追溯讲解视频。
5.2 自动化评测链路:RunLeetCodeFuncWithFile
a_test.go 展示了仓库的通用评测范式——把题解函数与用例文件交给测试工具:
func Test_a(t *testing.T) { if err := testutil.RunLeetCodeFuncWithFile(t, firstUniqueEven, "a.txt", 0); err != nil { t.Fatal(err) } }其中RunLeetCodeFuncWithFile定义于 leetcode/testutil/leetcode.go,它的工作流程可以拆解为:
- 读取用例文件
a.txt; - 通过
trimSpaceAndEmptyLine剔除空行与首尾空白; - 用反射(
reflect.TypeOf(f))取得被测试函数的输入参数个数fNumIn与返回值个数fNumOut; - 按"每
fNumIn + fNumOut行一组"切分用例,即一行输入 + 一行期望输出为一组(a.txt中[3,4,2,5,4,6]与2构成一组,[4,4]与-1构成一组); - 逐组调用
RunLeetCodeFuncWithExamples执行题解并比对输出。
这种"源码函数 + 文本用例文件"的测试架构(同一套工具还支持类题、多输出、指定用例号、超时检测isTLE等能力,详见 leetcode/testutil/leetcode_test.go),让仓库中上千道题解都能以几乎零模板代码的方式获得回归测试保障——firstUniqueEven本身就是一个最小的可复现示例。
5.3 如何在本仓库运行该题的测试
在仓库根目录执行 Go 测试命令即可复现上述用例:
go test ./leetcode/biweekly/178/a/ -run Test_a -v若两个样例全部通过,测试会逐条报告Case 1、Case 2的结果。你也可以向 a.txt 追加"输入行 + 期望输出行"的成对数据(需保证总行数能被 2 整除),来扩充自己的边界用例。
六、边界情况与易错点总结
基于解法本身与仓库用例,以下几点值得在实现与自测时重点核对:
- 无解返回 -1:
[4,4]这类"唯一元素却不唯一"的输入最容易踩坑,务必保留兜底return -1。 - "第一个"的语义:第二趟必须按
nums原序扫描,不能改为遍历哈希表键(哈希表无序,会破坏"第一个"的约束)。 - 偶数判定放在两处:计数阶段只统计偶数可以减小哈希表规模;查询阶段再次校验偶数可保证与计数口径一致(Python 的
Counter版本因统计了全部元素,查询阶段尤其不能省略x % 2 == 0)。 - 大数组性能:$n$ 规模较大时,两趟线性扫描 + 期望 $\mathcal{O}(1)$ 的哈希操作即为最优复杂度,无需排序(排序会引入 $\mathcal{O}(n \log n)$ 并破坏原序语义)。
七、从一题看一类:计数 + 顺序扫描的通用范式
firstUniqueEven是"频次统计 + 顺序查询"这一双趟范式的典型样本,同类题目往往只是筛选条件不同:
- 把"偶数"换成"奇数/正数/特定区间",思路不变;
- 把"恰好出现一次"换成"出现次数为 k",只需把
cnt[x] == 1改为cnt[x] == k; - 需要"任意一个"而非"第一个"时,第二趟甚至可以省略,直接遍历哈希键即可。
掌握了"先统计、后按序复查"的框架,再配合本仓库 copypasta 中丰富的模板(如计数桶、前缀和等基础数据结构),可以快速迁移到大量"存在性 + 频次"类题目上。
- 科学计算
【免费下载链接】codeforces-go
算法竞赛模板库 by 灵茶山艾府 💭💡🎈
相关推荐
3条命令装好50多个Codex技能:awesome-codex-skills完整指南
3条命令装好50多个Codex技能:awesome codex skills完整指南 awesome codex skills 是一个 Codex 技能精选合集
AI 技能AI 插件工作流自动化人工智能leetcode 每日一题 · 594. Longest Harmonious Subsequence(最长和谐子序列):哈希计数两轮遍历解法全解析
leetcode 每日一题 · 594. Longest Harmonious Subsequence(最长和谐子序列):哈希计数两轮遍历解法全解析 本篇技术指
文档教程知识库哈希集合求数组公共元素:LeetCode 2956「找到两个数组中的公共元素」多语言解法与 Go 实现剖析
哈希集合求数组公共元素:LeetCode 2956「找到两个数组中的公共元素」多语言解法与 Go 实现剖析 导读 本文以本仓库 leetcode/biweekl
科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考