【NOI】C++一维数组之数组计数法
文章目录
- 前言
- 一、概念
- 1.导入
- 2.概念
- 二、例题讲解
- 问题:2005. 选班委
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:1884. 求n个数中每个数出现的次数
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:1883. 求n个数中出现次数最多的数
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:1725. 声音识别
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:1334. 扑克牌组合
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:1178. COUNT
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:1180. 数字出现次数
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:1179. 求N个整数的平均数、众数和中位数
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:1183. 去除重复数字
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:1472. 找筷子
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:1557. 夏令营小旗手
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 问题:2029. 缺失的数字
- 1. 问题分析
- 2. 解题思路
- 3. 完整代码
- 三、总结
- 四、感谢
前言
在编程的世界里,有些问题看似复杂,实则只需要一个简单而巧妙的方法就能轻松解决。想象一下,当班主任需要统计全班同学的投票结果时,如果一个个画"正"字计数,既费时又容易出错。但如果我们为每个候选人准备一个专属的"投票箱",投票时直接投到对应位置,统计工作就变得异常简单。
这就是数组计数法的精髓所在——通过数组下标与待统计对象的对应关系,用最直接的方式完成统计任务。无论是选班委的投票统计、数字出现次数的统计,还是寻找缺失的数字,数组计数法都能以极高的效率解决问题。
本篇文章将带你系统学习这种简单而强大的编程技巧。我们将从最基础的概念入手,通过12个由浅入深的例题,让你彻底掌握数组计数法的应用场景和实现技巧。
学习大纲:C++全国青少年信息学奥林匹克竞赛(NOI)入门级-大纲
一、概念
1.导入
相信大家在读书时都会经历一件事情——选班委。
新学期开始,班主任老师会说:‘欢迎大家来到新班级,现在我们要选举班委来帮助管理班级。’
选举办法是这样的,首先让全班同学依次上讲台做自我介绍,然后按照职位一个一个依次进行选举,先选班长,再选学习委员……,选举办法是每人投一票,谁的票数最高就选谁担任这个职位。

怎么快速统计出投票结果呢?
想象一下,如果老师这样统计:
念到’张三’,就在黑板上’张三’后面画一横
念到’李四’,就在’李四’后面画一横
念到’王五’,就在’王五’后面画一横
…
这种方法虽然直观,但当候选人很多时(假设100人),找名字、画正字都很费时间。

今天我们要学习的’数组计数法’,就是一种让计算机帮我们快速统计的方法!
传统画正字法:
// 就像在黑板上找名字、画正字
张三:正
李四:正一
王五:正正
...
// 需要:找名字 → 画一笔 → 再找名字 → 再画一笔
数组计数法:
// 为每个候选人准备一个专属计数器
int a[50] = {0}; // 假设最多50个候选人// 统计过程:直接投到对应位置
a[1]++; // 1号候选人得1票
a[3]++; // 3号候选人得1票
a[1]++; // 1号候选人再得1票// 就像:1号投票箱 ← 投一票 ← 再投一票
// 3号投票箱 ← 投一票
2.概念
数组计数法是一种利用数组下标与待统计对象的对应关系,用数组元素存储对象出现次数的高效统计方法,核心适用于 “对象范围固定且连续” 的场景。
数组下标直接对应待统计对象的值,数组元素的值对应该对象的出现次数。
- 适用场景:
-
数据范围已知且固定(比如学号1-100,成绩0-100)。
-
数据可以直接映射为数组下标。
-
数据分布相对密集。
-
- 方法论:
-
1.建箱子:根据数据范围创建足够大的数组
-
2.投票数:遍历数据,在对应位置计数
-
3.看结果:遍历数组,输出非零结果
-
效率极高:无需排序、无需复杂查找,仅需两次遍历(一次计数、一次提取结果),时间复杂度为 O (n)。
二、例题讲解
问题:2005. 选班委
类型:
题目描述:
小 T 和他的小伙伴们到 CZ 中学的创新实验班报到后的第一件事就是选班委,班主任 R 老师走上讲台宣布了选举办法,首先让全班 40 位同学依次上讲台做自我介绍,然后按照 职位一个一个依次进行选举,先选班长,再选学习委员……,选举办法是每人投一票,谁 的票数最高就选谁担任这个职位,最后围棋高手小 W 颇具大将风范被选为班长,学神小 Z 当选为学习委员那是众望所归,小 S 则有天生一副好嗓子,不但歌唱得好,并且能将多种 动物的叫声模仿得惟妙惟肖,因此当选为文体委员。小 T 同学在本次选举中负责计票,他 觉得手工计票太慢了,且容易出错,因此想请你编一个程序实现机器计票功能。
这个程序 要能实现以下功能:全班共有 n 个同学,依次用 1 到 n 编号,共有 m 个人(包括班主任和 任课老师等)参与了投票,每张选票上写有一个同学的编号,得票最多的同学当选。
输入:
输入数据第一行包含两个用空格隔开的正整数 n 和 m,其中n≤200,m≤2000。
第二行有 m 个用空格隔开的不超过 n 的正整数,表示这 m 张选票上所写的编号。
输出:
输出得票最多的那个同学的编号。如果同时有两名以上同学得票最多,输出编号最小的那个同学的编号。
样例1:
输入:
3 4
1 3 2 1
输出:
1
样例2:
输入:
3 10
1 2 1 3 2 3 1 2 2 1
输出:
1
1. 问题分析
- 分析题目要求:统计 m 张选票中每个同学(编号 1~n)的得票数,找出得票最多的同学编号;若有多名同学得票相同且最多,输出编号最小的那个。
- 确定输入输出:输入第一行是两个正整数 n(同学总数)和 m(投票总数),第二行是 m 个不超过 n 的正整数(每张选票的同学编号);输出为得票最多(同票取编号最小)的同学编号。
- 运算规则:对选票中的同学编号进行计数,比较所有同学的得票数,筛选出得票最高且编号最小的同学。
2. 解题思路
- 整体思路概述:利用数组计数法统计每位同学的得票数,再遍历计数结果,找出得票最多且编号最小的同学。
- 关键步骤说明:
- 定义大小为 210 的计数数组a(下标对应同学编号 1~n,n≤200,数组大小足够覆盖),初始值为 0,用于存储每位同学的得票数。
- 读取 n 和 m,循环读取 m 张选票的编号t,每读取一个编号就执行a[t]++,完成得票计数。
- 定义变量mx(存储最大得票数,初始为最小整数)和mxi(存储得票最多的同学编号),遍历编号 1~n:
- 若当前同学的得票数a[i]大于mx,则更新mx为a[i],mxi为i;
- 因从编号 1 开始遍历,若遇到得票与mx相同的情况,不会更新mxi,天然保证 “同票取编号最小”。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// n:同学总数(编号1~n);m:投票总数;a[210]:计数数组,下标=同学编号,元素=对应得票数,初始为0int n,m,a[210]={0};// 读取同学总数n和投票总数mcin>>n>>m;// 循环读取m张选票,统计每位同学的得票数for(int i=0;i<m;++i){int t; // 临时存储当前选票的同学编号cin>>t;a[t]++; // 对应编号同学的得票数+1}// mx:存储最大得票数(初始为INT_MIN,确保能被首次得票数更新);mxi:存储得票最多的同学编号int mx=INT_MIN,mxi;// 遍历所有同学编号(1~n),找得票最多且编号最小的同学for(int i=1;i<=n;++i){// 只有当前得票数大于已记录的最大得票时,才更新最大得票和对应编号// 从小编号开始遍历,同票时不会更新,自然保留编号最小的if(a[i]>mx){mx=a[i]; // 更新最大得票数mxi=i; // 更新得票最多的同学编号} }// 输出得票最多(同票取编号最小)的同学编号cout<<mxi;return 0;
}
问题:1884. 求n个数中每个数出现的次数
类型:
题目描述:
从键盘读入 n 个整数(n≤100),这 n 个数都是 1 ~ 10 之间的数,请从小到大输出每个出现过的数,以及统计出每个数出现的次数?
比如:假设读入 5 个数,分别为 1 2 3 3 5 ,统计结果如下:1 出现 1 次,2 出现 1 次,3 出现 2 次,5 出现 1 次。
输出格式如下:
1 1
2 1
3 2
5 1
输入:
第 1 行输入一个整数 n 。
第 2 行输入 n 个整数,用空格隔开。
输出:
输出若干行,每行 2 个数,用空格隔开,按照从小到大的顺序,输出每个数以及每个数出现的次数。
样例:
输入:
5
1 2 3 3 5
输出:
1 1
2 1
3 2
5 1
1. 问题分析
- 分析题目要求:统计输入的 n 个整数(范围 1~10)中每个数的出现次数,按数字从小到大的顺序输出出现过的数及其对应次数。
- 确定输入输出:输入为整数 n(n≤100)和 n 个 1~10 的整数;输出为若干行,每行包含两个整数,分别是出现过的数和其出现次数,按数字升序排列。
- 运算规则:对输入的数字进行计数(每个数出现的次数),去重后按数字本身大小排序输出。
2. 解题思路
- 整体思路概述:利用数字范围固定(1~10)的特点,通过数组计数法高效统计每个数的出现次数,再按数字顺序输出结果。
- 关键步骤说明:
- 定义大小为 11 的数组a(下标 0 不用,1~10 对应输入的数字),初始值为 0,用于存储每个数的出现次数。
- 读取输入的 n 和 n 个整数,每读取一个数t,就将数组中对应下标t的元素值加 1(即a[t]++),完成计数。
- 从下标 1 到 10 遍历数组a,对每个元素值不为 0 的下标i(即出现过的数),输出i和a[i](该数及其出现次数),因遍历顺序是 1 到 10,天然保证了输出按数字从小到大排序。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// n:输入的整数个数;a[11]:计数数组,下标1~10对应数字1~10,元素值为对应数字的出现次数,初始化为0int n,a[11]={0};// 读取整数个数ncin>>n;// t:临时存储每次读取的整数int t;// 循环n次,读取每个整数并计数:每读到数字t,就将对应下标t的计数+1for(int i=0;i<n;++i){cin>>t;a[t]++; // 核心计数操作,记录数字t的出现次数增加1}// 从1到10遍历数组,输出所有出现过的数字(i)及其出现次数(a[i])// 因遍历顺序是1~10,直接满足“从小到大”的输出要求for(int i=1;i<11;++i){if(a[i]!=0) // 只输出出现过的数字(计数不为0)cout<<i<<" "<<a[i]<<endl;}return 0;
}
问题:1883. 求n个数中出现次数最多的数
类型:
题目描述:
从键盘读入 n 个整数( n≤100 ),这 n 个数都是 1∼10 之间的数,请求出出现次数最多的数是哪个数?
比如:假设读入 5 个数,分别为 1 2 3 3 5 ,出现次数最多的数就是 3 。(本题的数据确保出现次数最多的数只有 1 个,不存在多个数出现的次数都最多的情况)
输入:
第 1 行输入一个整数 n 。
第 2 行输入 n 个整数,用空格隔开。
输出:
输出出现次数最多的数。
样例:
输入:
5
1 2 3 3 5
输出:
3
1. 问题分析
- 分析题目要求:统计输入的 n 个整数(范围 1~10)中每个数的出现次数,找出出现次数最多的那个数(题目保证唯一)。
- 确定输入输出:输入为整数 n(n≤100)和 n 个 1~10 的整数;输出为出现次数最多的数。
- 运算规则:对每个数进行计数,比较所有数的出现次数,选出次数最大的数。
2. 解题思路
- 整体思路概述:利用数字范围固定(1~10)的特点,通过数组计数法统计每个数的出现次数,再遍历计数结果找到次数最多的数。
- 关键步骤说明:
- 定义大小为 11 的数组a(下标 1~10 对应数字 1~10,元素值为对应数字的出现次数,初始为 0)。
- 读取 n 和 n 个整数,每读取一个数t,就执行a[t]++,完成计数。
- 定义变量mx(存储最大出现次数,初始为最小整数)和mxi(存储出现次数最多的数),遍历 1~10 的数字:
- 若当前数字的出现次数a[i]大于mx,则更新mx为a[i],mxi为i。
- 因题目保证唯一最多次数,无需处理并列情况。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// n:输入的整数个数;a[11]:计数数组,下标1~10对应数字1~10,元素值为对应数字的出现次数,初始为0int n,a[11]={0};// 读取整数个数ncin>>n;// t:临时存储每次读取的整数int t;// 循环n次,读取每个整数并计数:每读到数字t,对应下标t的计数+1for(int i=0;i<n;++i){cin>>t;a[t]++; // 核心计数操作,记录数字t的出现次数增加1} // mx:存储最大出现次数(初始为INT_MIN,确保首次计数能更新);mxi:存储出现次数最多的数int mx=INT_MIN,mxi;// 遍历1~10的数字,寻找出现次数最多的数for(int i=1;i<=10;++i){// 若当前数字i的出现次数大于记录的最大次数,更新最大次数和对应数字if(a[i]>mx){mx=a[i]; // 更新最大出现次数mxi=i; // 更新出现次数最多的数}}// 输出出现次数最多的数cout<<mxi;return 0;
}
问题:1725. 声音识别
类型:
题目描述:
学校王老师研发了一套新的声音识别系统,该系统预先将每位同学的学号和声音录入之后,只要同学一说话,就知道是哪个学号的同学在说话(不同的同学声音是完全不一样的)。
王老师将系统装在了教室,系统收集到了一节英语课中同学们说话的 n 个片段,并转换为了同学们的学号,请你编程帮助王老师计算出,有多少个同学在英语课中发过言,并按照学号由小到大,输出每位同学的学号及该学号的同学发言的次数。
比如,假设经过系统分析,得到如下的 8 个同学的学号分别是:
8 1 6 1 8 6 7 2,表示同学们的发言顺序分别是:8 号、1 号、6 号、1 号、8 号、6 号、7 号、2 号,那么一共有 5 位同学发过言,按照学号由小到大输出发言次数如下:
1 2
2 1
6 2
7 1
8 2
分别代表 1 号同学发言 2 次、2 号同学发言 1 次、6 号同学发言 2 次、7 号同学发言 1 次、8 号同学发言 2 次。
输入:
输入有 2 行,第 1 行有一个整数 n(3≤n≤10000);
第二行有 n 个数字(这些整数都是 1-100 之间的整数,含 1和100),数字之间用空格隔开,代表同学们的学号。
输出:
第 1 行输出 1 个整数,代表总共有多少名同学说过话。
接下来输出若干行,每行 2 个整数,用空格隔开,表示按照学号由小到大输出发言同学的学号及每位同学发言的次数。
样例1:
输入:
8
8 1 6 1 8 6 7 2
输出:
5
1 2
2 1
6 2
7 1
8 2
样例2:
输入:
10
9 8 8 1 3 6 6 6 9 10
输出:
6
1 1
3 1
6 3
8 2
9 2
10 1
1. 问题分析
- 核心要求:统计发言同学总数,按学号升序输出每位同学的学号及发言次数。
- 输入输出:输入为整数 n 和 n 个 1-100 的学号;输出先给出发言同学总数,再依次输出排序后的学号与对应次数。
- 运算规则:对学号进行去重统计(计数),结果需按学号从小到大排序。
2. 解题思路
- 整体思路概述:借助数组下标与学号的一一对应关系,直接计数无需额外排序,时间和空间效率都很高。
- 关键步骤说明:
- 初始化大小为 110 的数组(覆盖 1-100 所有可能学号),初始值为 0。
- 遍历输入的每个学号,让对应数组下标元素自增 1(实现计数)。
- 遍历数组统计非零元素个数,即为发言同学总数。
- 从下标 1 到 100 遍历数组,输出所有非零元素对应的下标(学号)和元素值(次数)。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// n存储发言片段的总数;a[110]为计数数组,下标对应学号(1-100),元素值对应发言次数,初始化为0int n,a[110]={0};// 读取发言片段的总数ncin>>n;// t用于临时存储每次读取的学号int t;// 循环n次,读取每个发言的学号并计数:每读到学号t,就将对应数组位置的计数+1for(int i=0;i<n;++i){cin>>t;a[t]++; // 核心计数操作,记录学号t的发言次数增加1}// c用于统计发过言的同学总数(即计数数组中值不为0的元素个数)int c=0;// 遍历所有可能的学号(1-100),统计非零计数的数量(发过言的同学)for(int i=1;i<=100;++i){if(a[i]!=0) {++c; // 遇到发过言的同学,总数+1}}// 输出发过言的同学总数cout<<c<<endl;// 再次遍历1-100的学号,按从小到大的顺序输出每个发过言的学号及其发言次数for(int i=1;i<=100;++i){if(a[i]!=0) {cout<<i<<" "<<a[i]<<endl; // 输出学号i和对应的发言次数a[i]}}return 0;
}
问题:1334. 扑克牌组合
类型:
题目描述:
小明从一副扑克牌中(没有大小王, J 认为是数字 11 , Q 是 12 , K 是 13 , A 是 1 )抽出 2 张牌求和,请问能够组合出多少个不相等的数,按照由小到大输出这些数。
输入:
第一行是一个整数 n 代表( n≤52 )扑克牌的总数量;
第二行的 n 个整数分别代表扑克牌的数值。
输出:
第一行是一个整数 m 代表组合出不相等的数字个数;
第二行 m 个数用空格隔开代表这m 个由小到大排序的不相等的数。
样例:
输入:
4
3 1 2 4
输出:
5
3 4 5 6 7
1. 问题分析
- 分析题目要求:从给定的 n 张扑克牌中,任意抽取 2 张计算它们的和,需找出所有不相等的和,统计其个数并按从小到大的顺序输出。
- 确定输入输出:输入为整数 n(牌的数量)和 n 个整数(每张牌的数值,范围 1-13);输出为不相等的和的个数 m,以及这 m 个按升序排列的和。
- 运算规则:计算所有 “不同两张牌” 的组合(i<j,避免重复计算同一对牌)的和,对这些和去重后排序。
2. 解题思路
- 整体思路概述:通过两层循环生成所有两张牌的组合的和,利用数组记录每个和是否出现(去重),最后统计并按顺序输出结果。
- 关键步骤说明:
- 用数组a存储输入的扑克牌数值。
- 设计数组b(下标范围 2-26)记录和的出现情况:最小和为 1+1=2,最大和为 13+13=26,下标对应和的值, 元素值非 0 表示该和出现过。
- 两层循环遍历所有 i<j 的组合,计算a[i]+a[j],并将b[和]标记为非 0(表示该和存在)。
- 遍历数组b,统计非 0 元素的个数(即不同和的数量),再按从小到大的顺序输出这些非 0 元素的下标(即不同的和)。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// n:扑克牌总数;a[52]:存储每张牌的数值;b[30]:记录和的出现情况(下标为和,值非0表示存在该和)// 注:和的范围是2(1+1)到26(13+13),故b的大小设为30足够覆盖int n,a[52],b[30]={0};// 读取扑克牌总数ncin>>n;// 读取n张牌的数值,存入数组afor(int i=0;i<n;++i){cin>>a[i];}// 两层循环计算所有两张牌的组合(i<j,避免重复)的和,并标记到b中for(int i=0;i<n-1;++i){ // 第一张牌的下标i从0到n-2for(int j=i+1;j<n;++j){ // 第二张牌的下标j从i+1到n-1(确保i<j,不重复)int sum = a[i] + a[j]; // 计算当前两张牌的和b[sum] = 1; // 标记该和存在(无需计数,只需标记是否出现)}}// 统计不同和的个数cint c=0;for(int i=2;i<=26;++i){ // 遍历所有可能的和(2到26)if(b[i]!=0){ // 若该和存在++c;}}// 输出不同和的个数cout<<c<<endl;// 按从小到大的顺序输出所有不同的和(遍历2到26,输出存在的和)for(int i=2;i<=26;++i){if(b[i]!=0){cout<<i<<" ";}}return 0;
}
问题:1178. COUNT
类型:
题目描述:
一本书的页数为 N ,页码从 1 开始编起,请你求出全部页码中,用了多少个 0,1,2…9 。
输入:
一个正整数 N ( N≤10000 ),表示总的页码。
输出:
共十行:第 k 行为数字 k−1 的个数。
样例:
输入:
11
输出:
1
4
1
1
1
1
1
1
1
1
1. 问题分析
- 分析题目要求:统计从 1 到 N(N≤10000)的所有页码中,数字 0-9 各自出现的总次数,按数字 0 到 9 的顺序,每行输出对应数字的出现次数。
- 确定输入输出:输入为正整数 N(页码总数);输出为 10 行,第 1 行是数字 0 的出现次数,第 2 行是数字 1 的出现次数,……,第 10 行是数字 9 的出现次数。
- 运算规则:遍历 1 到 N 的每个页码,拆分每个页码的每一位数字(如页码 10 拆分为 1 和 0),累加每个数字(0-9)的出现次数。
2. 解题思路
- 整体思路概述:通过遍历每个页码,拆分其每一位数字并统计,最终按顺序输出 0-9 的出现次数,核心是 “逐位拆分计数”。
- 关键步骤说明:
- 定义计数数组a[10](下标 0-9 对应数字 0-9,元素值为对应数字的出现次数,初始为 0)。
- 遍历所有页码(1 到 N):对每个页码i,用变量t临时存储i,通过循环拆分t的每一位(t%10获取当前个位数字,t/=10移除当前个位),每拆分出一个数字,就将计数数组中对应位置的元素值加 1。
- 遍历计数数组a[0]到a[9],依次输出每个元素值(即 0-9 的出现次数)。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// n:总页码数;a[10]:计数数组,下标0-9对应数字0-9,元素值为对应数字的出现次数,初始为0int n,a[10]={0};// 读取总页码数ncin>>n;// t:临时存储当前页码,用于拆分每一位数字int t;// 遍历1到n的所有页码for(int i=1;i<=n;++i){t=i; // 将当前页码i存入t,避免修改i本身// 拆分t的每一位数字:循环直到t变为0(所有位都拆分完毕)while(t){int digit = t % 10; // 取t的个位数字(如t=123时,digit=3)a[digit]++; // 该数字的出现次数+1t /= 10; // 移除t的个位(如t=123变为12,继续拆分十位、百位)}}// 按数字0到9的顺序,输出每个数字的出现次数for(int i=0;i<=9;++i){cout<<a[i]<<endl;}return 0;
}
问题:1180. 数字出现次数
类型:
题目描述:
有 50 个数( 0∼19),求这 50个数中相同数字出现的最多次数为几次?
输入:
50 个数字。
输出:
1 个数字(即相同数字出现的最多次数)。
样例:
输入:
1 10 2 0 15 8 12 7 0 3 15 0 15 18 16 7 17 16 9 1 19 16 12 17 12 4 3 11 1 14 2 11 14 6 11 4 6 4 11 13 18 7 0 3 2 3 18 19 2 16
输出:
4
1. 问题分析
- 分析题目要求:给定 50 个范围在 0~19 之间的数字,统计其中所有数字的出现次数,找出相同数字出现的最多次数并输出。
- 确定输入输出:输入为 50 个 0~19 的整数;输出为一个整数,即所有数字中出现次数的最大值。
- 运算规则:先对 50 个数字进行计数(统计每个数字出现的次数),再从所有计数结果中筛选出最大值。
2. 解题思路
- 思路概述:利用数字范围固定(0~19)的特点,通过数组计数法统计每个数字的出现次数,最后遍历计数数组找到最大值,即为答案。
- 关键步骤说明:
- 定义大小为 20 的计数数组a(下标 0~19 对应数字 0~19,元素值为对应数字的出现次数,初始为 0)。
- 循环 50 次,读取每个输入的数字t,执行a[t]++,完成 50 个数字的计数。
- 定义变量mx(存储最大出现次数,初始为最小整数),遍历计数数组a[0]~a[19],不断更新mx为当前数组元素中的最大值。
- 输出mx,即相同数字出现的最多次数。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// t:临时存储每次读取的数字;a[20]:计数数组,下标0~19对应数字0~19,元素值为对应数字的出现次数,初始为0int t,a[20]={0};// 循环50次,读取50个数字并计数for(int i=1;i<=50;++i){cin>>t; // 读取当前数字a[t]++; // 对应数字的出现次数+1}// mx:存储相同数字出现的最多次数,初始为INT_MIN(最小整数),确保能被首次计数更新int mx=INT_MIN;// 遍历0~19所有数字的计数结果,找到最大出现次数for(int i=0;i<=19;++i){if(mx<a[i]){ // 若当前数字的出现次数大于已记录的最大值mx=a[i]; // 更新最大值}}// 输出相同数字出现的最多次数cout<<mx;return 0;
}
问题:1179. 求N个整数的平均数、众数和中位数
类型:
题目描述:
求 N 个整数的平均数,众数和中位数。
小知识:
1.众数
如有 9个数:17,13,17,9,17,17,3,16,17,其中 17 出现的次数最多,即为这组数的众数。(本题确保测试数据中,出现次数最多的数只有一个)
2.中位数
如有9个数:102,170,96,90,97,106,110,182,100.
将这 9个数按一定的顺序(从大到小或从小到大)排列后得到:182,170,110,106,102,100,97,9690。正中间的一个数是 102,102 是这组数的中位数。
而这 10 个数:106,99,104,120,107,112,33,102,97,100.按一定顺序排列后得到:120,112,107,106,104,102,100,99,97,33.正中间有两个数:104,102,中位数就是这两个数的平均数,即(104+102)/2=103.0
输入:
第一行为整数 N ( 5≤N≤10000 );
第二行为空格隔开的 N 个数 Ai ( 0≤Ai≤100)。
输出:
输出空格隔开的平均数 众数 中位数(平均数保留两位小数,中位数保留一位小数)。
样例:
输入:
6
5 2 2 3 4 6
输出:
3.67 2 3.5
1. 问题分析
- 分析题目要求:对输入的 N 个整数(0≤Ai≤100),计算并输出三个统计量 —— 平均数(保留两位小数)、众数(出现次数最多的数,题目保证唯一)、中位数(保留一位小数)。
- 确定输入输出:输入第一行是整数 N(5≤N≤10000),第二行是 N 个空格隔开的整数;输出是三个数用空格隔开,依次为平均数、众数、中位数。
- 运算规则:
- 平均数 = 所有数的总和 ÷ N;
- 众数 = 出现次数最多的数(通过计数统计);
- 中位数:先将数组排序,若 N 为奇数,取排序后中间位置(下标 N/2)的数;若 N 为偶数,取中间两个位置(下标 N/2 和 N/2-1)的数的平均值。
2. 解题思路
- 整体思路概述:分三步计算三个统计量 —— 用 “累加求和” 算平均数,用 “数组计数法” 找众数,用 “排序 + 奇偶判断” 求中位数,最后按要求精度输出结果。
- 关键步骤说明:
- 读取数据并预处理:用数组a存储 N 个输入整数,用数组b(下标 0~100 对应数字 0~100)统计每个数的出现次数,同时累加所有数的和用于计算平均数。
- 计算平均数:总和除以 N,得到平均数avg。
- 查找众数:遍历计数数组b,找到出现次数最大的元素对应的下标(即众数mxi)。
- 计算中位数:用sort函数对数组a排序,根据 N 的奇偶性计算 —— 奇数取中间元素,偶数取中间两个元素的平均值。
- 控制输出精度:用fixed和setprecision分别控制平均数(两位小数)和中位数(一位小数)的输出格式。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// n:输入整数的个数;a[10010]:存储输入的n个整数(大小10010适配n≤10000)// b[110]:计数数组,下标0~100对应数字0~100,元素值为对应数字的出现次数,初始为0// avg:存储平均数(双精度浮点数,保证计算精度)int n,a[10010],b[110]={0};double avg=0;// 读取整数个数ncin>>n;// 读取n个整数,同时完成三件事:存到a数组、统计出现次数、累加求和for(int i=0;i<n;++i){cin>>a[i]; // 把当前读取的数存入数组ab[a[i]]++; // 统计该数的出现次数(数组计数法)avg+=a[i]; // 累加所有数的和,用于后续算平均数}// 计算平均数:总和 ÷ 整数个数navg/=n;// 查找众数:mx记录最大出现次数,mxi记录众数(出现次数最多的数)int mx=INT_MIN,mxi;for(int i=0;i<=100;++i){ // 遍历0~100所有可能的数字if(b[i]>mx){ // 若当前数字的出现次数大于记录的最大次数mx=b[i]; // 更新最大出现次数mxi=i; // 更新众数为当前数字}}// 计算中位数:先排序,再根据n的奇偶性计算sort(a,a+n); // 对数组a进行从小到大排序// 三元运算符判断n奇偶:奇数取中间元素,偶数取中间两个元素的平均值double zws=(n%2==0 ? (a[n/2]+a[n/2-1])/2.0 : a[n/2]);// 按要求精度输出结果cout<<fixed<<setprecision(2); // 设置输出精度为两位小数(用于平均数)cout<<avg<<" "<<mxi<<" "; // 输出平均数、众数cout<<fixed<<setprecision(1); // 调整输出精度为一位小数(用于中位数)cout<<zws; // 输出中位数return 0;
}
问题:1183. 去除重复数字
类型:
题目描述:
给你 N 个数(N≤100),每个数都在(0∼1000)之间,其中由很多重复的数字,请将重复的数字只保留一个,并将剩下的数由小到大排序并输出。
输入:
输入有 2 行;
第 1 行为 1 个正整数,表示数的个数:N;
第 2 行有 N 个用空格隔开的整数。
输出:
第 1 行为 1 个正整数 M,表示不相同数的个数。
接下来的 M 行,每行一个整数,表示从小到大排好序的不相同的数。
样例:
输入:
10
20 40 32 67 40 20 89 300 400 15
输出:
8
15
20
32
40
67
89
300
400
1. 问题分析
- 分析题目要求:对输入的 N 个数字(0~1000)进行去重(重复数字只保留一个),统计去重后不相同数字的个数 M,再将这些不相同的数字按从小到大的顺序输出。
- 确定输入输出:输入第一行是正整数 N(数的个数),第二行是 N 个 0~1000 的整数;输出第一行是 M(不相同数的个数),接下来 M 行每行一个整数(按升序排列的去重数字)。
- 运算规则:先对数字去重(标记每个数字是否出现),再统计去重后数字的个数,最后按升序输出去重后的数字。
2. 解题思路
- 整体思路概述:利用数字范围固定(0~1000)的特点,通过数组计数法实现去重和排序 —— 数组下标对应数字本身,元素标记数字是否出现,遍历数组时天然按下标升序,无需额外排序。
- 关键步骤说明:
- 定义大小为 1010 的数组a(下标 0~1000 对应数字 0~1000,初始值为 0,0 表示未出现,非 0 表示已出现)。
- 读取 N 和 N 个数字,每读取一个数字t,就将a[t]设为非 0(标记该数字已出现,实现去重)。
- 遍历数组a[0]~a[1000],统计元素非 0 的个数(即去重后不相同数字的个数 M),并输出 M。
- 再次遍历数组a[0]~a[1000],对每个元素非 0 的下标(即已出现的数字),逐行输出该下标(实现按升序输出去重后的数字)。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// n:输入数字的总个数;a[1010]:标记数组,下标0~1000对应数字0~1000,0=未出现,非0=已出现(实现去重)int n,a[1010]={0};// 读取数字总个数ncin>>n;// t:临时存储每次读取的数字int t;// 循环读取n个数字,标记每个数字是否出现(去重核心:重复数字只会标记一次)for(int i=0;i<n;++i){cin>>t;a[t] = 1; // 用1标记数字t已出现,重复数字再次读取时仍设为1,不影响结果}// c:统计去重后不相同数字的个数(即M)int c=0;for(int i=0;i<=1000;++i){if(a[i]!=0) ++c; // 元素非0表示数字i已出现,计数+1}// 输出不相同数字的个数Mcout<<c<<endl;// 按从小到大顺序输出去重后的数字(遍历下标0~1000,天然升序)for(int i=0;i<=1000;++i){if(a[i]!=0) cout<<i<<endl; // 输出已出现的数字i}return 0;
}
问题:1472. 找筷子
类型:
题目描述:
经过一段时间的紧张筹备,电脑小组的“RP餐厅”终于开业了,这天,经理 LXC 接到了一个定餐大单,可把大家乐坏了!员工们齐心协力按要求准备好了套餐正准备派送时,突然碰到一个棘手的问题,筷子!
CX 小朋友找出了餐厅中所有的筷子,但遗憾的是这些筷子长短不一,而我们都知道筷子需要长度一样的才能组成一双,更麻烦的是 CX 找出来的这些筷子数量为奇数,但是巧合的是,这些筷子中只有一只筷子是落单的,其余都成双,善良的你,可以帮 CX 找出这只落单的筷子的长度吗?
输入:
第一行读入一个数 N,它代表 CX 找到的筷子的根数。(10≤N≤10000)
第二行是 N 个用空格隔开的数,代表筷子的长度。(筷子长度都是 1∼1000 之间的整数)
输出:
一行,落单的筷子的长度。
样例:
输入:
9
2 2 1 3 3 3 2 3 1
输出:
2
1. 问题分析
- 分析题目要求:从 N 根筷子(N 为奇数)中找出唯一一根落单的筷子(出现次数为奇数),其余筷子均成双(出现次数为偶数),输出落单筷子的长度。
- 确定输入输出:输入第一行是整数 N(10≤N≤10000),第二行是 N 个 1~1000 的整数(筷子长度);输出为落单筷子的长度。
- 运算规则:统计每个长度的出现次数,其中只有一个长度的出现次数为奇数,其余均为偶数,找到该奇数次数对应的长度即可。
2. 解题思路
- 样例分析:
筷子:🏷️ 🏷️ 🏷️ 🏷️ 🏷️ 🏷️ 🏷️ 🏷️ 🏷️ 🏷️
长度: 2 2 1 3 3 3 2 3 1
计数: 2出现3次,1出现2次,3出现4次
落单: 2(出现奇数次)
-
整体思路概述:利用筷子长度范围固定(1~1000)的特点,通过数组计数法统计每个长度的出现次数,再遍历计数结果,找到出现次数为奇数的长度(即落单筷子)。
-
关键步骤说明:
- 定义大小为 1010 的计数数组a(下标 1~1000 对应筷子长度,元素值为对应长度的出现次数,初始为 0)。
- 读取 N 和 N 个筷子长度,每读取一个长度t,执行a[t]++,完成计数。
- 遍历数组a[1]~a[1000],寻找元素值为奇数的下标(因只有落单筷子的出现次数为奇数),该下标即为落单筷子的长度。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// n:筷子的总根数;a[1010]:计数数组,下标1~1000对应筷子长度,元素值为对应长度的出现次数,初始为0int n,a[1010]={0};// 读取筷子总根数ncin>>n;// t:临时存储每次读取的筷子长度int t;// 循环读取n根筷子的长度,统计每个长度的出现次数for(int i=0;i<n;++i){cin>>t;a[t]++; // 对应长度的出现次数+1} // 遍历所有可能的长度(1~1000),寻找出现次数为奇数的长度(落单筷子)for(int i=1;i<=1000;++i){if(a[i]%2==1) // 若该长度的出现次数为奇数(落单)t=i; // 记录该长度} // 输出落单筷子的长度cout<<t;return 0;
}
问题:1557. 夏令营小旗手
类型:
题目描述:
2015年江苏省《信息与未来》夏令营在洪泽县实验小学进行,组委会决定在洪泽县实验小学的学生中推选一名小旗手,推选方法如下:
洪泽县实验小学有 n 名学生(1 <n < 1000)。每名学生有一个学号,学号为 1,2,…n。同时,每名同学有一张选票,可以推选一名同学为小旗手。最后,得票最多者当选,若得票最多者相同票数,则学号小者当选。
例如,选票为2 3 4 4 3 4 1 6,4号学生得票最多(3 票)当选小棋手。
输入:
第 1行读入 n,x1两个整数,n为学生数,x1为第一个选票上的学号,之后的选票 xi(i> 2)由下面的递推关系给出:
xi=(xi-1x37+33031)mod n+1
其中 mod 为取余运算,例如,13 mod 8=5,21 mod 21 =0。根据这个公式,就能从x1推出x2,x3,…,xn.
输出:
一个整数,即选出的小旗手的学号。
样例:
输入:
5 2
输出:
2
样例说明:
选票为2 1 4 5 2
1. 问题分析
- 分析题目要求:有 n 名学生(学号 1~n),需根据递推公式生成 n 张选票(第一张为 x1),统计每张选票对应的学号得票数,选出得票最多的学号;若得票相同,选学号最小的作为小旗手。
- 确定输入输出:输入为两个整数 n(学生数)和 x1(第一张选票学号);输出为当选小旗手的学号。
- 运算规则:选票生成规则 ——xi=(xi-1×37+33031) mod n +1(i≥2);得票统计规则 —— 得票最多者当选,同票时学号小者优先。
2. 解题思路
- 整体思路概述:用数组计数法统计每张选票对应的学号得票数,先根据递推公式生成所有 n 张选票并完成计数,再遍历计数数组找到得票最多且学号最小的学生。
- 关键步骤说明:
- 定义大小为 1001 的计数数组a(下标 1~n 对应学生学号,元素值为对应学号的得票数,初始为 0)。
- 读取 n 和 x1(第一张选票学号),先将a[x1]加 1(统计第一张选票)。
- 循环 n-1 次(生成剩下的 n-1 张选票):根据递推公式计算当前选票 xi,同时将a[xi]加 1(统计得票)。
- 遍历计数数组a[1]~a[n],用mx记录最大得票数,mxi记录对应学号;因从小编号开始遍历,同票时不会更新mxi,天然保证 “同票选学号小者”。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
int main(){// n:学生总数(学号1~n);x:存储当前选票的学号;a[1001]:计数数组,下标=学号,元素=对应得票数,初始为0int n,x,a[1001]={0};// 读取学生数n和第一张选票的学号x1(存入x)cin>>n>>x;// 统计第一张选票的得票:x对应的学号得票数+1a[x]++;// 循环生成剩下的n-1张选票(i从2到n,共n-1次)for(int i=2;i<=n;++i){// 根据递推公式计算当前选票xi,结果存入xx=(x*37+33031)%n+1;// 统计当前选票的得票:xi对应的学号得票数+1a[x]++;}// mx:存储最大得票数(初始为0,因至少得票0);mxi:存储当选小旗手的学号int mx=0,mxi;// 遍历所有学号(1~n),找得票最多且学号最小的for(int i=1;i<=n;++i){// 若当前学号得票数大于记录的最大得票if(a[i]>mx){mx=a[i]; // 更新最大得票数mxi=i; // 更新当选学号}}// 输出当选小旗手的学号cout<<mxi;return 0;
}
问题:2029. 缺失的数字
类型:
题目描述:
有 n 个数字,值就是 1-n ,现发现丢失了 2 个数字,请你根据剩余的 n - 2 个数字,编程计算一下,缺失的是哪两个数字呢?
输入:
第 1 行为整数 N (N≤100000)。
第 2 行有 N−2 个整数(均用空格隔开)表示了剩下 N−2 个数字的值。
输出:
一行,按数字从小到大顺序输出两缺失的数字。两个数字之间用一个空格隔开。
样例:
输入:
6
4 3 1 5
输出:
2 6
1. 问题分析
- 分析题目要求:已知有 1~n 这 n 个连续数字,其中缺失了 2 个,根据剩余的 n-2 个数字,找出这两个缺失的数字,并按从小到大的顺序输出。
- 确定输入输出:输入第一行是整数 n(n≤100000),第二行是 n-2 个整数(1~n 范围内的剩余数字);输出为一行,两个按升序排列的缺失数字。
- 运算规则:判断 1~n 中哪些数字未在输入的 n-2 个数字中出现,未出现的即为缺失数字。
2. 解题思路
- 整体思路概述:利用数字范围固定(1~n)的特点,通过数组计数法标记已出现的数字,再遍历 1~n,找出未被标记的两个数字(即缺失数字),因遍历顺序天然为升序,直接输出即可。
- 关键步骤说明:
- 定义大小为 100001 的全局数组a(下标 1~n 对应数字 1~n,元素值 0 表示未出现,1 表示已出现,全局数组默认初始化为 0)。
- 读取 n 和 n-2 个剩余数字,每读取一个数字t,就将a[t]设为 1(标记该数字已出现)。
- 从 1 到 n 遍历数组a,依次找出元素值为 0 的下标(即未出现的缺失数字),并输出,因遍历顺序是 1~n,输出结果天然符合升序要求。
3. 完整代码
#include<bits/stdc++.h>
using namespace std;
// 全局数组a,大小100001(适配n≤100000),全局数组默认初始值为0
// 下标1~n对应数字1~n,a[t]=0表示数字t未出现,a[t]=1表示数字t已出现
int a[100001]={0};
int main(){// n:原始数字的总个数(1~n)int n;// 读取ncin>>n;// t:临时存储每次读取的剩余数字int t; // 循环读取n-2个剩余数字,并标记其已出现for(int i=0;i<n-2;++i){cin>>t;a[t] = 1; // 标记数字t已出现}// 遍历1~n,找出未出现的数字(a[i]=0)并输出,遍历顺序保证结果升序for(int i=1;i<=n;++i){if(a[i]==0){cout<<i<<" ";}} return 0;
}
三、总结
通过本文的学习,相信你已经对数组计数法有了全面而深入的理解。让我们回顾一下这种方法的精髓:
数组计数法的本质是建立数据与数组下标的直接映射关系,通过数组元素的值来记录对应数据的出现次数。这种方法将复杂的统计问题转化为简单的数组操作,实现了"所见即所得"的直观统计。
更重要的是,数组计数法教会我们一种重要的编程思维——利用数据结构直接表达问题关系。这种"空间换时间"的思想在后续学习哈希表、桶排序等高级算法时同样适用。
数组计数法就像编程世界里的"瑞士军刀",简单却功能强大。希望你在今后的编程实践中,能够灵活运用这种方法,让代码更加简洁高效。记住:最好的算法往往不是最复杂的,而是最适合问题的!

四、感谢
如若本文对您的学习或工作有所启发和帮助,恳请您给予宝贵的支持——轻轻一点,为文章点赞;若觉得内容值得分享给更多朋友,欢迎转发扩散;若认为此篇内容具有长期参考价值,敬请收藏以便随时查阅。
每一次您的点赞、分享与收藏,都是对我持续创作和分享的热情鼓励,也是推动我不断提供更多高质量内容的动力源泉。期待我们在下一篇文章中再次相遇,共同攀登知识的高峰!
欢迎关注本专栏《C++从零基础到信奥赛入门级(CSP-J)》

