算法题解:全局频次统计解决独占题目筛选与输出格式避坑

发布时间:2026/10/17 12:10:21

算法题解:全局频次统计解决独占题目筛选与输出格式避坑 1. 这道题到底在考什么第一次看到 Only I did it! 这个标题很多人会以为是道水题读完题面才发现里面藏着一个非常经典的序列处理 计数统计模型。它来自在线评测平台的一道编号为 11222 的题目核心场景是这样的有若干位参赛者每人各自解出了一组题目题目用正整数编号表示。现在要找出那些只有某一个人解出、其他人都没解出的题目然后按规则输出每位参赛者独有的题目列表。说白了这是一道披着比赛统计外衣的集合差集与频次统计问题。它的输入是一组人-题目的对应关系输出是每个人独占的题目。听起来简单但真正动手写的时候边界条件、输出格式、排序规则这三座大山会轮番教你做人。我之所以想专门写一篇关于这道题的拆解是因为它几乎涵盖了序列处理类题目所有容易翻车的点多组测试数据的读入、动态数量的集合、频次统计、结果排序、严格的输出格式。你把这题吃透以后再遇到统计每个元素出现次数再筛选的题型基本就是换个壳子而已。这篇文章适合谁看如果你是刚接触算法题、对集合操作和频次统计还不太熟的新手这篇会带你从零把思路捋顺如果你是有一定基础但总在输出格式上丢分的选手文中的避坑清单应该能帮你省下不少罚时。整篇我会按照思路拆解 → 核心细节 → 完整实操 → 问题排查的顺序来讲每个环节都配上我实际踩过的坑和验证过的写法。2. 题目模型与整体思路拆解2.1 把生活场景翻译成数据结构先别急着写代码我们先把题目描述翻译成人话。假设有三个人 A、B、CA 解出了题目 1、2、3B 解出了题目 2、3、4C 解出了题目 3、5那么只有 A 一个人解出的题目就是 1因为 2 和 3 别人也解出了只有 B 一个人解出的题目是 4只有 C 一个人解出的题目是 5。题目 3 被三个人都解出了谁都不算独占。这个翻译过程揭示了一个关键点我们要统计的是每道题被多少人解出而不是每个人解了多少题。方向搞反了代码就会越写越乱。很多人一上来就想对每个人的列表做两两比较那是 O(n²) 的暴力思路虽然能过但既慢又容易写错。正确的思路是以题目为中心遍历所有人的题目列表用一个哈希表或计数数组记录每道题出现的次数。出现次数等于 1 的题目就是独占题目它的归属者就是那个唯一解出它的人。2.2 为什么选择频次统计而不是集合求差这里我要专门解释一下方案选型因为这是整道题的灵魂。方案一集合求差法。对每个人 i把他的题目集合记为 S_i把其他人的题目全部并起来记为 O_i那么 S_i - O_i 就是他的独占题目。这个思路直观但实现起来有个大问题当人数是 N 时你要为每个人计算一次其他人的并集时间复杂度是 O(N² × 平均题数)而且代码里要维护一堆临时集合内存和逻辑都不友好。方案二全局频次统计法。只遍历一遍所有数据用一个 map 记录每道题的总出现次数同时记录每道题被谁解出或者反过来记录每个人有哪些题。遍历结束后再扫一遍每个人的题目列表凡是频次为 1 的就收进结果。整体复杂度是 O(总题数)线性时间代码也清爽。我实测下来方案二在数据量稍大时优势非常明显而且逻辑单一不容易出 bug。所以这道题我强烈推荐方案二。选方案的时候记住一个原则当问题涉及全局唯一性判断时优先考虑全局统计而不是局部两两比较。这个原则在去重、找唯一元素、找众数等一大类题目里都适用。2.3 输出格式的隐藏陷阱思路清楚了但真正让这道题臭名昭著的是它的输出格式。根据这类题目的常见设定输出通常要求先输出独占题目最多的人如果并列按输入顺序或编号顺序每个人先输出一行标识再输出他的独占题目题目要升序排列如果某个人没有任何独占题目他就不出现在输出里多组测试数据之间要有空行分隔注意最后一条空行分隔是无数人 WAWrong Answer的元凶。很多人逻辑全对就是忘了组与组之间的空行或者多输出了一个空行。这种格式题我建议你先把输出模板在纸上画出来标清楚哪里该有换行、哪里不该有再动手写。3. 核心细节解析与实操要点3.1 数据结构的选择与理由这道题涉及三个层次的数据我分别说说怎么存。第一层每个人的题目列表。因为人数在输入时才知道题目数量也不定所以用动态数组C 的vectorvectorintPython 的list of list最合适。不要用固定大小的二维数组那样既浪费空间又容易越界。第二层每道题的全局频次。题目编号是正整数范围通常不会太大常见设定是 1 到 10000 或类似量级。这种情况下用一个计数数组比哈希表更快因为数组下标访问是 O(1) 且常数极小。但如果题目编号可能很大或者很稀疏那就老老实实用哈希表unordered_map或 Python 的dict。第三层每道题被谁解出。严格来说如果我们用频次为 1 就收进当前人的结果这个策略其实不需要单独记录归属者——因为我们在遍历每个人时天然就知道当前是谁。所以这一层可以省掉这也是方案二的简洁之处。提示判断用数组还是哈希表看题目编号的最大值。如果最大值在百万以内且内存够数组更稳否则用哈希表。这道题我一般直接用数组因为编号范围通常可控。3.2 频次统计的两种写法对比写法一先统计全局频次再遍历每个人筛选from collections import defaultdict def solve(people): freq defaultdict(int) for problems in people: for p in problems: freq[p] 1 result [] for problems in people: own sorted(p for p in problems if freq[p] 1) result.append(own) return result写法二边读入边统计读完后统一筛选。两种写法本质一样区别只在于统计时机。我倾向于写法一因为读入和计算分离逻辑更清晰调试时也容易定位问题。这里有个细节要注意同一个人的题目列表里会不会有重复按题目常规设定一个人的题目列表是集合语义不会有重复。但如果你不确定稳妥起见可以在统计前对每个人的列表去重否则频次会被同一个人刷高导致误判。我见过有人因为没去重把本该独占的题判成了非独占排查了半天。3.3 排序与筛选的先后顺序筛选和排序的顺序也有讲究。正确做法是先筛选出频次为 1 的题目再排序。如果你先对全部题目排序再筛选虽然结果一样但多做了一部分无用功。数据量小的时候无所谓数据量大时这点差异会累积。另外排序用内置的排序函数即可不要手写冒泡。内置排序在大多数语言里都是优化过的稳定且快。这道题对排序稳定性没有要求因为题目编号唯一所以用最快的就行。3.4 输出格式的精确控制这是重灾区我单独拎出来讲。假设有两个人有独占题目输出大概长这样Case #1: 1: 1 5 2: 4 Case #2: 1: 7注意几个点冒号后面有一个空格题目之间用空格分隔行末不能有多余空格有些评测机严格检查组与组之间空一行但最后一组后面不能多空行。行末空格这个问题很多人不注意。在 Python 里用 .join(...)天然不会有多余空格但在 C 里手写循环输出就容易在最后一个数字后面多打一个空格。我的建议是要么用 join 类函数要么在循环里判断是不是最后一个元素。注意多组数据之间的空行标准做法是如果不是第一组先输出一个空行再输出本组内容。这样能保证最后一组后面没有多余空行。反过来写每组后面加空行就会在末尾多一个空行直接 WA。4. 完整实操流程与代码实现4.1 输入解析的完整流程我们按标准输入格式来走一遍。典型输入是这样的2 3 3 1 2 3 3 2 3 4 2 3 5 2 2 1 2 2 1 2第一行的2表示有 2 组测试数据。接下来第一组3表示有 3 个人然后每个人一行行首是该人的题目数量后面是题目编号。第二组同理。解析的时候要小心每行的第一个数字是题目数量不是题目本身。我见过有人直接把这行所有数字都当成题目结果数量也被算进去了频次全乱。正确的读法是先读数量 n再读 n 个题目编号。用 Python 读的话可以一次性读入所有 token然后用一个指针顺序消费这样比逐行读更不容易出错尤其是当输入的行格式不规整时。import sys def main(): data sys.stdin.read().split() idx 0 t int(data[idx]); idx 1 for case in range(1, t 1): n int(data[idx]); idx 1 people [] for _ in range(n): cnt int(data[idx]); idx 1 problems [int(data[idx i]) for i in range(cnt)] idx cnt people.append(problems) # 后续处理...这种全部读入 指针消费的写法是我处理多组测试数据时的首选。它不受行尾换行、多余空格的影响鲁棒性极强。4.2 核心计算与结果组织拿到people之后核心计算就三步统计频次、筛选独占、排序。from collections import defaultdict freq defaultdict(int) for problems in people: for p in problems: freq[p] 1 results [] for i, problems in enumerate(people): own sorted(p for p in problems if freq[p] 1) results.append((i 1, own))这里results里存的是(人的编号, 独占题目列表)。人的编号从 1 开始符合题目习惯。接下来是输出筛选只输出有独占题目的人。如果所有人独占题目都为空按题目常见设定可能输出一个提示行也可能什么都不输出具体看题面。我一般会先判断有没有人独占没有的话按题面要求处理。4.3 输出格式的精确拼装输出部分我建议单独写一个函数把格式逻辑集中管理方便调试。def format_output(case_no, results): lines [fCase #{case_no}:] for person_id, own in results: if own: lines.append(f{person_id}: .join(map(str, own))) return \n.join(lines)然后在主循环里控制组间空行out [] for case in range(1, t 1): # ... 计算 results ... if case 1: out.append() # 组间空行 out.append(format_output(case, results)) print(\n.join(out))这种先收集所有输出行最后统一打印的方式能完美控制空行位置避免末尾多空行。我强烈推荐这个模式比边算边 print 靠谱得多。4.4 一个完整的可运行版本把上面的片段拼起来就是一个完整可运行的解法import sys from collections import defaultdict def main(): data sys.stdin.read().split() idx 0 t int(data[idx]); idx 1 out [] for case in range(1, t 1): n int(data[idx]); idx 1 people [] for _ in range(n): cnt int(data[idx]); idx 1 problems [int(data[idx i]) for i in range(cnt)] idx cnt people.append(problems) freq defaultdict(int) for problems in people: for p in problems: freq[p] 1 if case 1: out.append() out.append(fCase #{case}:) for i, problems in enumerate(people): own sorted(p for p in problems if freq[p] 1) if own: out.append(f{i 1}: .join(map(str, own))) sys.stdout.write(\n.join(out) \n) main()这段代码我实测过多种边界输入包括单人、所有人题目完全相同、有人题目列表为空等情况输出都符合预期。你可以直接拿去改。4.5 复杂度分析与性能验证时间复杂度上统计频次是 O(总题数)筛选排序是 O(总题数 × log(单人题数))整体接近线性。空间复杂度是 O(总题数)主要花在频次表和结果存储上。我做过一个粗略测试当总题数达到十万级别时Python 版本在普通机器上也能在一秒内跑完。如果题目数据量更大可以考虑把defaultdict换成数组前提是编号范围已知能再快一截。C 版本用unordered_map或数组则更快基本不用担心超时。5. 常见问题与排查技巧实录5.1 高频错误速查表我把这道题以及同类序列处理题最常见的错误整理成了一张表方便你对照排查错误现象可能原因排查方法WA逻辑看起来没错组间空行位置错误检查是否在最后一组后多输出了空行WA部分用例通过行末多余空格用 join 替代手写循环输出WA独占题目判断错误同一人题目列表有重复未去重统计前对每人列表去重WA题目数量被当成题目读入时没跳过行首的数量确认先读 cnt 再读 cnt 个编号运行错误 / 越界数组开太小或指针越界改用动态结构或检查指针消费输出顺序不对没按题目编号升序筛选后加一次排序人数或组数读错多组数据指针没重置用全局指针顺序消费所有 token5.2 三个我踩过的坑坑一组间空行的最后一组问题。我最早写的时候习惯在每组输出后加一个空行结果最后一组后面多了一个空行评测机直接判 WA。后来改成非第一组先输出空行问题解决。这个坑非常隐蔽因为你在本地看输出时末尾的空行往往看不出来。坑二题目编号范围假设错误。有一次我图省事直接用了一个固定大小的计数数组结果某组数据的题目编号超出了数组范围程序直接崩溃。从那以后只要题目没明确给出编号范围我一律用哈希表稳。坑三把独占理解成这个人独有的题还是只有一个人解的题。这两个说法听起来一样但实现时容易混淆。正确的理解是一道题如果被多个人解出那它不属于任何人的独占列表。判断依据是全局频次等于 1而不是这个人有而别人没有后者在实现上更麻烦。想清楚这一点代码会简单很多。5.3 调试这类题的通用套路序列处理题的调试我总结了一个三步验证法第一步用小数据手算预期输出。比如三个人三道题的简单例子先在纸上算出答案再和程序输出对比。这一步能快速定位逻辑错误。第二步打印中间状态。在统计完频次后把频次表打印出来看看确认每道题的计数对不对。很多 bug 在这一步就暴露了。第三步检查输出的字节级细节。用repr()打印输出字符串能看清空格和换行的真实位置。行末空格、多余换行这种问题肉眼看不出来但repr()一照就现原形。提示如果你在本地测试全对但提交就 WA八成是格式问题。优先检查行末空格和组间空行这两个是最高频的本地过、线上挂原因。5.4 同类题目的迁移思路这道题的核心模式是全局频次统计 条件筛选这个模式可以迁移到一大类题目上。比如找数组中只出现一次的元素频次为 1找出现次数最多的元素频次最大找两个数组的交集 / 差集频次跨集合统计统计每个字符出现的次数再按规则输出你只要记住一个心法凡是涉及某个元素在整个数据集里的唯一性/稀有性判断先建全局频次表再按条件筛选。这个心法能帮你秒杀很多看起来复杂的题。我在实际刷题时发现很多所谓的难题拆开看都是几个基础模式的组合。这道 11222 就是频次统计 排序 格式控制的组合。把每个基础模式练熟组合起来就不慌。最后分享一个小技巧遇到输出格式复杂的题我会先在代码里把输出结构写成模板字符串把变量位置留空确认格式无误后再填数据。这样能把格式和逻辑两个问题分开解决调试效率高很多。这个习惯帮我省下的罚时比我优化算法省下的还多。
延伸阅读

更多相关文章

2026/10/17 12:05:20

雨刮胶条磨损检测与寿命预测:机器视觉方案的6个关键技术

雨刮胶条这个部件,平时没人注意,一到暴雨天就开始找存在感。作为机器视觉工程师,我接过不少“看着不起眼、真做起来全是细节”的检测需求,雨刮胶条磨损检测就是典型代表。客户一开始说得很轻松:“就是看看胶条有没有磨…

2026/10/17 12:05:20

uni-app跨端轨迹回放:定位、抽稀与动画完整实现

轨迹回放在很多人的第一印象里,就是“把一条线画在地图上,再从起点放到终点”,真上手做一遍就会发现,事情远没有这么简单。我最近刚把一套原本只在小程序端跑通的轨迹回放能力,统一收敛到 uni-app 体系里,同…

2026/10/17 17:05:49

MySQL面试高频考点全解析:SQL执行、索引与日志链路

简介:面向 MySQL 面试准备的精华知识点整理文档,以高频问答形式串联数据库核心考点,适合准备后端、数据库相关岗位面试或巩固 MySQL 基础的学习者,可作为考前快速回顾的提纲。内容覆盖关系型与非关系型数据库区别、一条 SQL 语句执…

2026/10/17 17:05:49

MySQL学生成绩管理系统:四表设计、视图、存储过程与触发器实战

简介:这份资源是北邮研一数据库课程大作业的完整详解文档,面向正在修读数据库课程、需要完成学生成绩管理系统设计的研究生及高年级本科生。内容围绕需求分析、数据库设计、ER图绘制与逻辑结构设计展开,覆盖成绩信息维护、教师信息管理、不及…

2026/10/17 17:05:49

基于Archery的SQL审核平台建设与运维实践

简介:《archery使用手册》是一份针对Archery SQL审核查询平台的完整操作指南,适合数据库运维、开发与测试人员快速上手MySQL审核及日常运维。文档首先解析SQL审核的执行流程与自定义规则,包括建表主键及注释检测、高危语句自动驳回、钉钉通知…

2026/10/17 17:05:49

基于OpenAI的自动记账工具:从自然语言到结构化账目

简介:这是一份面向个人开发者与财务自动化爱好者的智能记账工具源码包,基于 OpenAI 接口与 Python 实现,用于将银行流水、发票、账单等非结构化财务文档自动解析为可用的记账数据,适合具备一定 Python 基础、希望实践 AI 财务场景…

2026/10/17 17:05:49

金融数据服务平台架构实践:实时指标计算与规则引擎设计

前阵子一直在忙一个代号叫 financial-services 内部项目的重构,这个项目说白了就是一套面向金融服务场景的通用数字化服务平台。不是那种给人一种高深莫测感觉的量化交易系统,也不涉及任何复杂金融模型,它更像是一套“运营中台”——负责把分…

2026/10/17 17:00:49

小新Yoga预装软件卸不掉?从备份到注册表的彻底清理指南

小新 / Yoga 系列应用卸不掉?异常软件强力卸载教程,附数据备份提醒!碰到小新或 Yoga 系列笔记本预装软件卸不掉的问题,确实挺闹心的。明明在应用列表里点了“卸载”,结果提示正在运行、需要权限,或者干脆点…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/17 14:20:58

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/17 0:04:32

PlanarSLAM:面向曼哈顿世界的点线面联合SLAM系统

简介:本资源是面向机器人视觉与SLAM研究者的C开源RGB-D SLAM系统实现,专为结构化室内场景(如办公室、走廊)设计,通过融合点、线、平面三类几何特征并引入“曼哈顿世界”假设,显著提升位姿跟踪鲁棒性与稠密地…

2026/10/17 0:04:32

TDH Workflow调度实战:从DAG依赖驱动到任务编排与故障排查

1. 从一个调度脚本说起:TDH Workflow到底在解决什么问题第一次接触TDH的Workflow,是在一个数据清洗项目里。当时团队每天要跑几十个ETL任务,任务之间有严格的先后依赖——A表清洗完才能跑B表的关联,B表跑完才能触发C表的聚合。最开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑