C++数据核心B4157:频次统计与计数数组入门解析
1. 题目背景与本质解读1.1 “数据核心”这个标题到底在考什么打卡记录刷到第 2812 题编号 B4157选了这道名字很唬人的“数据核心”。先说结论这道题其实没有它名字听起来那么复杂它的本质可以浓缩成四个字——频次统计。题目的大致设定是给你一串整数让你找出这串数据里出现次数最多的那个数然后输出它本身以及它出现的次数。如果出现次数最多的数不止一个那就选数值最小那个。这在小学生组 C 竞赛里是很常见的考法但它同时也是一个很典型的“筛选题”。为什么这么说因为题面里的每一个限制条件都不是无端加进来的。并列时取最小、数据范围负到正、输入长度上限这三个点只要你漏掉任意一个代码要么跑错要么直接越界崩溃。很多刚上手信奥的同学对题目的态度是“看懂样例就开写”。这种做法在简单题上还能侥幸过关但到了这种带边界条件的统计题往往就是栽跟头的地方。信奥题给的是规则不是故事你需要做的第一件事永远是拆解规则把自然语言翻译成精确的逻辑表达式。“数据核心”去掉修辞外壳后只剩下三件事统计频率、寻找最大、处理并列。1.2 这道题在刷题路线里的位置我说它是入门题但“入门”不代表“不值得做”。恰恰相反这类题目是整个信奥知识体系的承重墙。你后面学排序、学二分、学哈希、学树状数组底层逻辑里都绕不开“把数据组织起来然后快速查询”这个动作。而这道题最朴素的版本就是让你用最直接的方式完成“组织”和“查询”这两个步骤。另外这类题目非常适合放进打卡刷题列表里。因为它的代码量不长几分钟就能完成一次完整的读题、编码、测试、提交闭环很适合培养竞赛手感。但我的建议是不要只满足于一遍过。你至少应该把三种不同的实现思路都写一遍感受一下它们在代码结构、运行速度、空间占用上的差异。这个习惯一旦养成比你多刷三百道重复题目有用得多。2. 解题前的关键抉择用哪条路线2.1 先吃透数据范围再谈算法信息学竞赛里有句经常被提到的话算法选得好不好不由你的喜好决定由数据范围决定。拿到题目第一个动作不是打开编译器而是拿出笔把输入规模、数值范围、时间限制这三个数字圈出来。B4157 这道题里常规设定是 N 不超过 10000单个数值落在 -100000 到 100000 之间。这个范围给了我们一个很强的信号值域是连续且已知的总共只有 200001 种可能。这意味着什么意味着我们可以用一个长度为 200001 的数组把每个输入值直接映射到数组下标下标位置记录它出现的次数。整个过程不需要排序也不需要复杂的查找结构。如果数值范围扩大到 10⁹数组方案就废了因为谁也不可能开一个十亿长度的数组。如果值域不确定那就要考虑哈希表。这些判断不能靠感觉必须靠对数据范围的敏感度。这也是刷题量积累的核心收益之一——你见得多了一眼就能定方案。2.2 三种常见解法的对比我把这道题的常见解法整理了一下你对照着看会很清楚方案时间复杂度空间复杂度适用场景代码难度暴力双层循环O(N²)O(1)N 极小几百低计数数组O(N M)O(M)M 为值域大小值域连续且可控低哈希表unordered_map平均 O(N)O(K)K 为不同值的个数值域大或不确定中暴力双层循环的思路是最朴素的外层遍历每一个数内层再遍历一遍整个数组数一数它出现了多少次最后维护一个最大值。这个方案的时间复杂度是 O(N²)当 N 达到 10000 时最坏情况要做一亿次比较。虽然数据量小的时候确实能跑但在竞赛场景里这属于把简单问题做复杂而且完全没体现出“数据组织”的思想。计数数组是我最推荐初学者掌握的方案。它利用数组随机访问 O(1) 的特性把统计过程压缩成一次遍历。你来一个数我就往对应的格子里加一最后沿着数组扫一遍边扫边记录最大值和对应的值。只要偏移量处理正确几乎不可能写错。哈希表在这里属于“能过但没必要”。它的优势是对值域范围没要求可以自动只存储出现过的数字。但它有额外的哈希计算开销在 200001 个连续整数面前它的常数比数组下标访问大得多。而且无序哈希表的遍历顺序不固定你在处理“并列取最小”的时候还得额外记录最小值代码反而更绕。2.3 负数值与偏移量的处理数组下标不能是负数这是这道题给新手的第一道坎。数值范围是 -100000 到 100000所以我们需要一个偏移量 OFFSET把负数抬到非负区间。这本质上是一次坐标平移把所有数字整体加上 100000映射到 [0, 200000] 的空间里。两个转换公式必须刻进脑子里写入计数时cnt[x OFFSET]还原数值时value index - OFFSET我见过不少同学统计部分写对了输出时忘了把下标减回去结果答案全带偏移量。这种错误在你做完十道题之后回想起来会觉得特别幼稚但在考场当下紧张和手忙脚乱叠加就是会让你漏掉这一个小细节。解决方案很简单把 OFFSET 定义成全局常量并且在写还原公式的时候顺手写一行注释长时间盯着代码的人是不会犯这种低级失误的。3. 完整 C 实现与逐步拆解3.1 全局数组和读入优化先给出一个可以直接提交的完整版本。我尽量把每一处关键的代码都配上说明让初学者能照着理解而不是照着抄。#include iostream using namespace std; const int OFFSET 100000; const int MAX_RANGE 200001; // 全局变量默认全部初始化为 0 // 下标范围 0 ~ 200000对应数值 -100000 ~ 100000 int cnt[MAX_RANGE]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N; cin N; for (int i 0; i N; i) { int x; cin x; cnt[x OFFSET]; } int bestValue -OFFSET; int bestCount -1; for (int i 0; i MAX_RANGE; i) { if (cnt[i] bestCount) { bestCount cnt[i]; bestValue i - OFFSET; } } cout bestValue bestCount \n; return 0; }这段代码的核心模块只有三个读入、统计、扫描输出。读入和统计合并在一趟循环里完成因为计数数组本身就是一个可以在线更新的数据结构。你来一个数我立刻登记不需要把原始数据先存起来。这一点跟暴力法有本质区别——暴力法必须把原始数组留着反复扫描而计数数组只需要保留汇总信息就够了。3.2 扫描时“严格大于”的巧妙之处很多同学在选最优值时习惯写if (cnt[i] bestCount)。这道题里这个写法就是典型的错误示范。因为题目要求“出现次数并列时取数值较小”而我们扫描数组下标是从小到大进行的。如果你用那么遇到后面更大的数值时即使它的计数跟当前最优值一样大也会覆盖掉前面的记录最终输出的就是较大的那个值。用就可以完美规避这个问题只有当某个数值的出现次数严格大于当前最优记录时才更新最优值。这样扫描结束时存下来的永远是下标最小、同时计数最大的那个数值。一句话总结帮你实现了“并列优先取小”不需要再额外写任何判断分支。这个写法是我强烈推荐掌握的。因为它不是针对这道题的奇技淫巧而是“顺序扫描时要保持首个最大值”这一通用技巧。后面你写滑动窗口、写单调栈、写各种贪心维护最优值时都会反复用到这种“非严格不更新”的思想。3.3 样例推导过程拿一组简单数据验证一下逻辑。假设输入6 3 5 -1 3 5 4统计表如下数值出现次数-11324152出现两次的数字有 3 和 5按照题目规则取较小的 3所以正确答案是3 2。再看代码的执行过程扫描到 3 对应的位置时cnt 值为 2更新最优为 3扫描到 5 对应的位置时cnt 值也是 2但因为不满足cnt[i] bestCount所以保持不变。输出3 2与预期完全一致。这种手动推导非常值得做。初学者往往依赖在线评测系统告诉你答案对错但你有必要先学会自己判断对错。拿出一张纸把样例输入跑一遍代码的逻辑流程画出每一个变量的变化过程这比任何调试工具都更能帮你建立程序思维。4. 刷题过程中的高频踩坑点4.1 数组开小了RE 还是 WA第一种经典错误数组长度只开到 100001忽略负数部分。当你输入 -100000 时x 100000等于 0看起来好像没事可当你输入 100000 时下标 200000 越过数组边界轻则访问到未知内存重则段错误崩溃。这就是运行错误 RE 的常见来源。还有一种反向错误数组长度开对了但偏移量写成了负数。比如cnt[x - OFFSET]正数会跑到数组前部负数会变成巨大的下标程序跑起来就爆炸。这类错误的排查思路其实很简单在代码开头加一个断言或者针对 -100000、100000 这两个边界值单独测试能跑通再提交。4.2 全局数组和局部数组的选择很多人习惯把所有变量都写在 main 函数里这在小规模题目里没问题但在这道题上我建议把大数组放到全局区。原因有两点第一全局数组由系统自动初始化为 0你省去手动 memset 的步骤第二局部变量存放在栈区栈空间通常在几 MB 量级一旦数组开大比如超过几百万个整数直接栈溢出崩溃。有些同学喜欢用vectorint cnt(MAX_RANGE)放在 main 里这也是一种方案。vector 的对象在堆上分配不会爆栈而且有自动初始化代码也很干净。我只是觉得对于这种固定范围的计数场景直接开一个全局原生数组是最干脆的既不用 include vector 头文件又能体现 C 面向系统底层的直接性。4.3 输出缓冲endl 和 \n 的区别初学者最容易忽略的输出细节是\n和endl的差异。两者从显示效果看都是换行但 C 底层逻辑完全不同。endl的本质是输出换行符之后再冲刷一次输出缓冲区而\n只是纯换行不冲刷缓冲区。在大量输出场景里频繁刷缓冲会大幅拖慢程序运行速度。虽然这道题只输出一行用哪个都无伤大雅但我强烈建议你从一开始就统一用\n。这个习惯在后续题目里会帮你省下很多时间。同样的道理读入方面ios::sync_with_stdio(false)和cin.tie(nullptr)这两行我每一道题都会写已经变成肌肉记忆了。4.4 常见错误速查表症状可能原因排查方法程序崩溃RE数组长度不足检查是否覆盖 -100000 到 100000程序崩溃RE偏移量符号写反用边界值测试答案错误WA并列条件用了 改成严格大于答案错误WA输出时没还原下标确认使用i - OFFSET答案错误WA初始化 bestCount 为 0改成 -1避免计数恰好为 0 时判断出错最后这一条容易被忽略如果把 bestCount 初始化为 0而某个数字出现次数恰为 0那么判断条件cnt[i] 0恒不成立程序依然能工作但如果出现次数本身可能为 0 且你希望保留“至少出现一次”这个前提用 -1 作为初始值在逻辑上更严谨。在这道题里每个输入值至少出现一次所以 0 和 -1 都不会出问题但统一写成 -1 是更专业、更稳妥的习惯。5. 从这道题延伸到更广的统计场景5.1 数据规模变大时的升级路径B4157 只是一个小切口。如果同样的题面把 N 改成 10⁶把数值范围改成 -10⁹ 到 10⁹那计数数组就直接失效了因为没人能开一个长度为 2×10⁹ 的数组。此时标准的替代方案是std::unordered_map只在出现过的数值上开桶统计。#include iostream #include unordered_map using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N; cin N; unordered_mapint, int freq; for (int i 0; i N; i) { int x; cin x; freq[x]; } int bestValue, bestCount -1; for (auto p : freq) { if (p.second bestCount) { bestCount p.second; bestValue p.first; } } cout bestValue bestCount \n; return 0; }但是注意了这个版本有一个隐患它在处理“并列取最小”时需要额外的逻辑因为哈希表的遍历顺序是无序的不能依赖遍历顺序来保证“取小”。正确做法是在更新条件里加上p.second bestCount p.first bestValue的并列判断。这只是技术细节但很能说明一个道理同样的逻辑搬到不同的数据结构上边界条件的处理方式会完全不同。5.2 频次统计模型在工程里的影子不要觉得“统计出现次数最多”只是竞赛题。你在真实的工程场景里会反复遇到它。电商后台想找销量最高的商品日志分析系统想找最频繁出现的错误码编译器想找哪些变量名出现次数最多这些都是同一个模型的变种。工程里实现它可能是用 Redis 的哈希表配合原子计数也可能是用数据库的 GROUP BY 语句但底层思想跟这道题一模一样把数据映射到计数器上然后查找最大值。竞赛题的价值不在于让考生背住某道题的代码而在于彻底理解这个“映射-计数-查询”的抽象过程。你在这道题里亲手写一遍数组下标映射以后看任何频次统计的方案都能一眼识别出它的本质。5.3 把一道简单题做出复杂效果我在平时的刷题复盘里有个固定的操作每做完一道题不急着标记完成而是做三件事。第一尝试用另一种数据结构重写第二把数据范围改大看当前的代码还能不能过第三故意引入一种经典错误观察程序输出强化对错误症状的记忆。比如这道题我至少会写四个版本计数数组版、unordered_map 版、map 版、暴力版。然后把四个版本放在同一组随机数据上跑记录耗时差异。这个过程大约花二十分钟但对数据结构和复杂度的理解比刷十道模拟题都深刻。打卡刷题的意义从来不是完成数量指标而是通过一个又一个具体题目把模糊的概念变成肌肉记忆。B4157 这道“数据核心”就是这样一个适合反复咀嚼的标本。你把它吃透了后面遇到的统计类题目对你来说都只是换了一层壳。

相关新闻

react-native-shake鸿蒙化改造:摇一摇传感器事件监听全解析

react-native-shake鸿蒙化改造:摇一摇传感器事件监听全解析

1. 为什么要把 react-native-shake 搬进 OpenHarmony1.1 摇一摇这个交互在 App 里到底承担什么角色做 React Native 鸿蒙化适配这段时间,我碰到最多的需求不是“把页面跑起来”,而是“把那个能用的小功能库搬过去”。react-native-shake 就是典型代表。名…

2026/10/17 14:13:03 阅读更多 →
OSPF单区域与多区域实战:从配置到排错的深度解析

OSPF单区域与多区域实战:从配置到排错的深度解析

1. 从一个"看起来通了"的网络说起很多人第一次接触动态路由协议,都是从配置单区域开始的。两台路由器、三台路由器,配好接口地址,敲几行network命令,邻居关系起来了,路由表里出现了对端网段,ping…

2026/10/17 14:13:03 阅读更多 →
个人初始化 Linux 服务器的 SOP

个人初始化 Linux 服务器的 SOP

在配置系统文件的时候,我写下了这句话,因为这个坑让我排查了若干小时: 所有的全局配置,必须写在文件的前半部分。所有的 Match 规则,必须、必须、必须扔到配置文件的绝对最末尾! 概要 本文记录我初始化 Linux 服务器…

2026/10/17 14:13:03 阅读更多 →

最新新闻

codex针对java+vue的Prompt提示词:用TaoToken统一Key跑通前后端联调

codex针对java+vue的Prompt提示词:用TaoToken统一Key跑通前后端联调

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 15:00:39 阅读更多 →
Oracle 11g 升级 19C 实战避坑:DBUA 报错、RMAN 恢复与时区版本全解析

Oracle 11g 升级 19C 实战避坑:DBUA 报错、RMAN 恢复与时区版本全解析

简介:这份Oracle 11g到19C数据库升级详尽指导手册,面向具备一定运维能力的数据库管理员(DBA)及运维工程师,聚焦利用DBUA工具将11g单机数据库安全迁移至19C的完整流程。内容涵盖前期备份与恢复、升级路线选择、环境说明…

2026/10/17 15:00:39 阅读更多 →
ZooKeeper节点类型详解:持久、临时、顺序节点的原理与实战避坑

ZooKeeper节点类型详解:持久、临时、顺序节点的原理与实战避坑

我这几年在分布式系统上踩的坑,有一大半都跟ZooKeeper的节点类型有关。不管是做分布式锁、服务注册发现,还是元数据存储,选错了节点类型,轻则功能异常,重则线上事故。今天就把这四种节点的特点、原理和使用场景彻底讲透…

2026/10/17 15:00:39 阅读更多 →
好写作AI:期刊论文选题到投稿全流程实操指南

好写作AI:期刊论文选题到投稿全流程实操指南

凌晨三点,对着屏幕上那块光标闪了一晚上的空白文档,手边的咖啡已经第三杯了。讲实话,这种场景在科研生活里太常见了:Introduction写不出逻辑链、文献综述堆了几十篇却不知道从哪句话开始、明明实验数据都齐了结果讨论部分憋不出来…

2026/10/17 15:00:39 阅读更多 →
Spring Boot内嵌Tomcat启动链路:原理、定制与避坑实战

Spring Boot内嵌Tomcat启动链路:原理、定制与避坑实战

简介:《Spring Boot启动过程(四)之内嵌Tomcat启动》是一份面向中高级Spring Boot开发者的PDF解析文档,帮助厘清启动阶段内嵌Web容器的创建、配置与初始化链路。文档以源码调用顺序为主线,先解释createEmbeddedServletC…

2026/10/17 15:00:39 阅读更多 →
分布式计算与并行计算区别详解:选型与实战指南

分布式计算与并行计算区别详解:选型与实战指南

我一直觉得,很多人一听到“分布式计算”和“并行计算”,第一反应是“这不差不多嘛,都是把任务拆开同时算”。实际上这两个概念在大数据处理里的定位差异非常大,用错场景轻则性能没提升,重则整套架构白搭。这篇文章我就…

2026/10/17 14:59:38 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →