- 科学计算
【免费下载链接】codeforces-go
算法竞赛模板库 by 灵茶山艾府 💭💡🎈
本篇题解对应 leetcode/season/2023spring2/a/README.md,完整讲解「力扣杯 2023 春季战队赛」中runeReserve(符文储备)一题的贪心式排序扫描解法。文章不仅还原了原文档的思路、四种语言代码与复杂度分析,还结合本仓库的 Go 实现、样例测试文件 与 测试工具链 给出源码级佐证。读完你将掌握一类经典套路:对数组排序后,通过相邻元素差值扫描,统计"差值不超过给定阈值的最长连续段"。
题目背景:力扣杯 2023 春·战队赛
runeReserve出自力扣杯 2023 春季战队赛(测试文件中注释的题目编号 为W2ZX4X)。题目大意是:给定一组符文(runes,整数数组),要求从中选出若干符文,使得其中任意两个符文之间的差都不超过 1,求最多能选出多少符文。
一个关键的观察是:满足"任意两数相差不超过 1"的集合,在排序后必然呈现为相邻元素差值不超过 1 的连续段。因此问题被转化为:对数组排序后,找到最长的"相邻差值 ≤ 1"的连续子段长度。这也是 LeetCode 中"分组循环"思想的典型应用——不需要复杂数据结构,一次线性扫描即可解决。
核心思路:排序 + 相邻差值扫描
原文档给出的思路非常简洁,只有两条规则:
- 如果相邻元素之差大于 1,说明当前连续段被打断,重新统计(计数器重置为 1);
- 否则计数器加一,并用它更新答案的最大值。
由于排序后数组单调不减,任意两个元素之差不超过 1 的充要条件可以按相邻元素逐对判定:只要连续段内部每对相邻元素差值都不超过 1,那么该段内任意两元素之差也一定不超过 1(传递性由单调性保证)。因此仅需一趟O(n)的相邻比较,就能得到全局最长段。
四种语言实现对照
原文档给出了 Python、Java、C++、Go 四种等价实现,细节略有差异,这里逐一说明:
class Solution: def runeReserve(self, runes: List[int]) -> int: runes.sort() ans = cnt = 1 for pre, cur in pairwise(runes): if cur - pre > 1: cnt = 1 # 重新统计 else: cnt += 1 ans = max(ans, cnt) return ansPython 版本利用pairwise迭代器直接生成相邻元素对,逻辑最贴近自然语言描述:差值大于 1 就重置,否则累加并更新答案。
class Solution { public int runeReserve(int[] runes) { Arrays.sort(runes); int ans = 1, cnt = 1; for (int i = 1; i < runes.length; i++) if (runes[i] - runes[i - 1] > 1) cnt = 1; else ans = Math.max(ans, ++cnt); return ans; } }Java 版本通过下标索引前后元素,++cnt先自增再比较,是竞赛中常见的紧凑写法。
class Solution { public: int runeReserve(vector<int> &runes) { sort(runes.begin(), runes.end()); int ans = 1, cnt = 1; for (int i = 1; i < runes.size(); i++) if (runes[i] - runes[i - 1] > 1) cnt = 1; else ans = max(ans, ++cnt); return ans; } };C++ 版本与 Java 完全同构,sort修改原数组后原地扫描。
func runeReserve(runes []int) int { sort.Ints(runes) ans, cnt := 1, 1 for i, n := 1, len(runes); i < n; i++ { if runes[i]-runes[i-1] > 1 { cnt = 1 // 重新统计 } else if cnt++; cnt > ans { ans = cnt } } return ans }Go 版本的else if cnt++; cnt > ans利用了 Go 语言if初始化语句的语法糖:先自增cnt,再判断是否超过当前最优值ans,等价于cnt++; if cnt > ans { ans = cnt }。
复杂度分析
原文档给出的结论为:
- 时间复杂度:
O(n log n),其中n为runes的长度。瓶颈在于sort排序,排序后的线性扫描只有O(n); - 空间复杂度:
O(1)。忽略排序使用的栈空间,仅用到ans、cnt与循环下标等若干额外变量。
值得强调的是,本题没有更优的复杂度下界可行路径:答案段依赖于"相邻差值 ≤ 1"的全局比较,若不排序则无法在线性时间内确定任意两元素的差值约束,因此排序是必需的一步。
仓库源码佐证:Go 实现与测试链路
该题在仓库中的完整 Go 代码位于 a.go,与 README 中的 Go 版本逐行一致:sort.Ints排序后从i = 1开始逐对比较,差值大于 1 时重置cnt = 1,否则累加并更新ans。
围绕该实现,仓库提供了一条完整的自动化测试链路,值得单独说明:
样例测试文件 a.txt:以"输入行 + 输出行"交替的方式存放测试数据,共两组样例:
[1,3,5,4,1,7]→ 输出3(排序后为[1,1,3,4,5,7],最长连续段[1,1]或[3,4,5],长度均为 3);[1,1,3,3,2,4]→ 输出6(排序后为[1,1,2,3,3,4],相邻差值均不超过 1,整个数组即为答案段)。
测试入口 a_test.go:调用
testutil.RunLeetCodeFuncWithFile读取a.txt中的用例驱动函数执行,同时调用testutil.RunFuncWithRandomInput用随机输入对实现做性质校验,防止存在未覆盖的边界场景。底层工具 leetcode/testutil/leetcode.go:
RunLeetCodeFuncWithFile先读取文件并剔除空白行,再按"函数参数个数 + 返回值个数"(本函数为1 + 1 = 2行一组)切分为独立用例,最终通过反射机制调用被测试函数并与期望输出比对。从源码结构看,这套工具对任意签名的 LeetCode 题解函数都是通用的,targetCaseNum参数支持指定运行某一个用例(如-1表示最后一个用例),便于本地调试定位。
仓库中该题目录 2023spring2 下还包含b/e等其余战队赛题目的同构目录(README.md+ 实现 + 测试),体现了"每题一份 README 题解 + 一份可运行代码 + 一份可回归测试"的沉淀模式,是研究竞赛题解与测试基础设施的极佳素材。
边界情况与易错点
- 空数组与单元素数组:答案初始化为
1,即至少可以选出 1 个符文(题目保证非空时该初值成立);若数组长度为 1,循环体不执行,直接返回 1。若题目可能出现空数组,需在初始化前特判为 0。 - 重复元素:重复符文之间的差值为 0,满足约束,属于同一个连续段,
a.txt的第二组样例正是靠大量重复元素把整个数组连成一段。 - 重置的时机:必须是"相邻差值大于1"才重置,等于 1(或 0)都要继续累加,这是判定是否断开的唯一标准。
- 更新答案的位置:差值大于 1 重置
cnt = 1时不需要更新ans(因为ans至少为 1),只有累加成功后才需要比较,这保证了任何输入下返回值的正确性。
综上,runeReserve是一道"排序 + 单次扫描"的经典入门题:它训练了将无序集合约束转化为有序数组相邻判定的抽象能力,也是后续"分组循环""最长连续段"类题目的最小可复现模板。配合仓库内的代码与测试用例,你可以在本地直接运行 a_test.go 复现全部验证过程。
- 科学计算
【免费下载链接】codeforces-go
算法竞赛模板库 by 灵茶山艾府 💭💡🎈
相关推荐
codeforces-go 题解精读:力扣双周赛 147 第 3 题「相邻差非递增的最长子序列」——多维 DP 与后缀最大值优化实战
codeforces go 题解精读:力扣双周赛 147 第 3 题「相邻差非递增的最长子序列」——多维 DP 与后缀最大值优化实战 本篇技术指南以 leetc
科学计算子序列 DP 的「枚举选哪个」与「值域 DP」:力扣 115 场双周赛 T3「最长不等相邻组子序列 II」详解(codeforces-go 实战)
子序列 DP 的「枚举选哪个」与「值域 DP」:力扣 115 场双周赛 T3「最长不等相邻组子序列 II」详解(codeforces go 实战) 本篇以力扣第
科学计算codeforces-go 仓库实战:力扣双周赛 168 A 题「反转后字典序最小字符串」的暴力与后缀数组解法
codeforces go 仓库实战:力扣双周赛 168 A 题「反转后字典序最小字符串」的暴力与后缀数组解法 导读 本文以 leetcode/biweekly
科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考