- 科学计算
【免费下载链接】codeforces-go
算法竞赛模板库 by 灵茶山艾府 💭💡🎈
本篇文章围绕力扣第 315 场周赛第一题(findMaxK,即求数组中同时存在k与-k的最大正整数)展开,以仓库 leetcode/weekly/315/a/README.md 的官方题解为主体骨架,完整保留其哈希表思路、四种语言(Python3 / Java / C++ / Go)的参考实现与复杂度分析,并结合 codeforces-go 仓库中同目录下的真实源码、测试文件与测试框架源码,深入讲解这道题的写法细节、微优化技巧以及仓库内“题目 → 实现 → 测试数据 → 自动验证”的完整闭环。读者读完可以掌握这类“配对 / 对称查找”题目的哈希表套路,并了解本仓库单题目录的标准组织方式与测试运行机制。
题目背景:周赛 315 的 A 题
这道题位于仓库leetcode/weekly/315/目录下的a/子目录中,对应力扣第 315 场周赛的第一题(简单题),英文题名为Largest Positive Integer That Exists With Its Negative(从 a_test.go 末尾的注释可以确认)。该目录为每道周赛题提供了一个标准化的四件套结构:
a.go:Go 语言题解实现(findMaxK函数)a_test.go:调用仓库测试框架自动跑样例的测试文件a.txt:本题的输入输出测试数据README.md:题解说明(思路、多语言代码、复杂度分析)
题意描述
给定一个整数数组nums,找到最大的正整数k,满足数组中同时存在k和-k。如果不存在这样的整数,返回-1。
例如:
- 输入
nums = [-1,2,-3,3]:3和-3同时存在,所以答案是3; - 输入
nums = [-1,10,6,7,-7,1]:7和-7同时存在(1和-1也存在),取最大者,答案为7; - 输入
nums = [-10,8,6,7,-2,-3]:不存在互为相反数的一对数字,返回-1。
这三个用例都保存在 a.txt 中,前两行为一组输入 + 期望输出,共三组数据。
核心思路:一边遍历,一边查哈希表
题目要求的是「配对」,即对任意x,只要它和某个已见过的数字互为相反数,就构成一个合法候选。因此关键观察是:
遍历到
x时,如果之前已经出现过-x,那么x与-x这一对就同时存在于数组中,|x|就是一个合法答案候选。
算法流程如下:
- 用哈希表(集合)记录所有出现过的数字;
- 初始化答案
ans = -1; - 从左到右遍历
nums:- 若
-nums[i]已经在哈希表中,说明找到了配对,用|nums[i]|更新答案的最大值; - 把
nums[i]加入哈希表(注意先查询后插入,避免把当前元素自身当成已出现的配对);
- 若
- 遍历结束,返回
ans。
由于只需要一次线性扫描,且哈希表的插入与查询都是平均O(1),整体复杂度非常理想(详见下文复杂度分析)。此外,把答案初值设为-1是一种常见技巧:它既是“不存在时”的返回值,也让更新逻辑统一为取最大值,无需单独处理空集情况。
四种语言的参考实现
原题解文档给出了 Python3、Java、C++、Go 四种语言的参考实现,这里完整保留并逐段注释:
Python3
class Solution: def findMaxK(self, nums: List[int]) -> int: ans = -1 s = set() for x in nums: if -x in s: ans = max(ans, abs(x)) s.add(x) return ansJava
class Solution { public int findMaxK(int[] nums) { int ans = -1; var s = new HashSet<Integer>(); for (int x : nums) { if (s.contains(-x)) ans = Math.max(ans, Math.abs(x)); s.add(x); } return ans; } }C++
class Solution { public: int findMaxK(vector<int> &nums) { int ans = -1; unordered_set<int> s; for (int x: nums) { if (s.count(-x)) ans = max(ans, abs(x)); s.insert(x); } return ans; } };Go
func findMaxK(nums []int) int { ans := -1 has := map[int]bool{} for _, x := range nums { if abs(x) > ans && has[-x] { ans = abs(x) } has[x] = true } return ans } func abs(x int) int { if x < 0 { return -x }; return x }四种实现的思路完全一致,区别仅在于语法与个别写法细节。
从源码看细节:Go 版实现的微优化
仓库中实际的 Go 实现位于 leetcode/weekly/315/a/a.go,与 README 中的 Go 版本一致。对比其他语言版本,Go 版本有一个值得注意的细节:
if abs(x) > ans && has[-x] { ans = abs(x) }其他语言版本是ans = max(ans, abs(x))(即无条件求abs(x)再比较),而 Go 版本先判断abs(x) > ans再决定是否查询哈希表、是否更新答案。这样做的效果是:
- 减少无谓的哈希查询:只有当当前元素的绝对值超过已有答案时,才需要查
has[-x],避免对大量不可能更新答案的元素做集合查询; - 避免重复计算
abs(x):条件成立时才第二次调用abs(x)赋值,而其他版本即使不更新答案也会计算一次绝对值; - 保持答案单调性:因为
ans只会越变越大,abs(x) > ans的剪枝不影响正确性。
这个微优化对单题而言收益有限,但体现了竞赛代码中“先判后算”的常见风格:在可读性不受损的前提下,把昂贵的操作放在条件分支里延迟执行。
另外,Go 版使用map[int]bool{}作为集合,has[-x]读取不存在键时返回零值false,恰好满足“未出现过”的语义,无需contains方法,这也是 Go 语言写集合类题目的惯用做法。
复杂度分析
- 时间复杂度:
O(n),其中n为nums的长度。整个数组只被扫描一次,每次哈希表的插入和查询均为平均O(1); - 空间复杂度:
O(n),哈希表最多需要存储n个不同的数字。
这一复杂度结论同样适用于上述全部四种语言实现,因为它们在数据规模上的操作次数一致。对于n ≤ 10^5级别的周赛数据范围,这是非常充裕的解法。
仓库中的测试闭环:数据、框架与自动生成
本题目录下最有工程价值的部分,是它演示了 codeforces-go 仓库“题目 → 题解 → 测试数据 → 自动验证”的标准流程。
测试文件与数据格式
a_test.go 由模板工具自动生成(文件头注释标明Code generated by copypasta/template/leetcode/generator_test.go),核心只有一段:
func Test_a(t *testing.T) { targetCaseNum := 0 // -1 if err := testutil.RunLeetCodeFuncWithFile(t, findMaxK, "a.txt", targetCaseNum); err != nil { t.Fatal(err) } }测试数据保存在 a.txt 中,格式为每行一个参数,输入行与输出行交替:
[-1,2,-3,3] 3 [-1,10,6,7,-7,1] 7 [-10,8,6,7,-2,-3] -1即:每组用例由 1 行输入(数组)+ 1 行期望输出组成;RunLeetCodeFuncWithFile会根据被测试函数的参数个数fNumIn与返回值个数fNumOut自动计算每组行数(本题为 1 + 1 = 2 行),把文件切分成用例集合(见 leetcode/testutil/leetcode.go 中RunLeetCodeFuncWithFile的实现)。
测试框架如何工作
testutil是仓库为 LeetCode 题解专门打造的通用测试框架,RunLeetCodeFuncWithFile的工作流程可以概括为:
- 读取
a.txt,去掉空行与首尾空白; - 按
(入参个数 + 出参个数)为周期切分数据,构造examples; - 通过反射解析输入(
parseRawArg支持 int、切片、字符串、指针类型等,见 parseRawArg 实现); - 逐用例调用被测函数,将输出序列化后与期望输出比对(
assert.Equal),并支持超时检测(默认 2 秒,见 config.go 中的DebugTLE = 2 * time.Second与AssertOutput = true)。
targetCaseNum参数用于控制跑哪个用例:0表示跑全部用例并开启超时检测,-1表示只跑最后一个用例(调试用)。
测试用例如何生成
这类a_test.go/a.txt对并不是手工编写的。模板入口 copypasta/template/leetcode/generator_test.go 提供了TestWeekly测试,它会通过GetWeeklyContestID(0)自动定位下一场周赛,读取环境变量LEETCODE_USERNAME_ZH、LEETCODE_PASSWORD_ZH、LEETCODE_COMMENT登录力扣,抓取题目与样例,自动生成形如leetcode/weekly/<id>/a/的标准目录。这意味着仓库中的每一道题都遵循同一套目录规范:a.go写题解函数,a_test.go只负责把函数和a.txt交给testutil框架,从而实现全仓库统一的“数据驱动测试”。
延伸:除了哈希表,还能怎么做?
如果面试官要求给出替代方案,两种常见的思路是:
- 排序 + 双指针:先对数组排序,然后用左右指针找互为相反数的一对;
-10,8,6,7,-2,-3这类用例在排序后同样能正确返回-1。代价是排序本身为O(n log n),不如哈希表的O(n); - 排序 + 二分查找:对每个正数
x二分查找-x是否存在,复杂度同样为O(n log n)。
因此对于“只需判断配对是否存在”的问题,哈希表一次遍历是时间最优的方案。若题目进一步要求只允许使用O(1)额外空间(这是力扣上该题的一个常见 follow-up 变体),则只能在排序的基础上做双指针,此时空间复杂度降为O(1),时间升为O(n log n)——取舍点在于题目对空间的要求。
小结
findMaxK是典型的“哈希表 + 对称查找”入门题:一条线性扫描、一次O(n)复杂度、一个-1初始值,就足以干净地解决问题。而 codeforces-go 仓库围绕这道题展示的,不仅是简洁的 Go 解法(含先判后算的微优化),更是一套可复用的工程化做题流程——从 a.go 的实现、a.txt 的数据组织,到 a_test.go 与 testutil 框架的自动验证,再到 generator_test.go 的模板化生成,读者完全可以照着这个模式在自己的刷题仓库里建立“一题一目录、数据驱动测试”的规范。
- 科学计算
【免费下载链接】codeforces-go
算法竞赛模板库 by 灵茶山艾府 💭💡🎈
相关推荐
Betterfox Firefox 优化指南:简单几步实现浏览器提速与隐私增强
Betterfox Firefox 优化指南:简单几步实现浏览器提速与隐私增强 Betterfox 是一个开源的 Firefox 优化配置项目,把数十项经过验证
科学计算codeforces-go 仓库实战:力扣双周赛 165 Q1 "最小缺席正整数"——下界枚举 + 哈希集合的 O(n) 解法
codeforces go 仓库实战:力扣双周赛 165 Q1 "最小缺席正整数"——下界枚举 + 哈希集合的 O n 解法 本篇技术指南以算法竞赛模板库 co
科学计算codeforces-go 仓库实战:力扣双周赛 121 A 题「最小缺失整数」顺序前缀和 + 哈希判重全解
codeforces go 仓库实战:力扣双周赛 121 A 题「最小缺失整数」顺序前缀和 + 哈希判重全解 导读 本篇技术指南以 leetcode/biwee
科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考