每年GESP一级考完,总有学生跟我吐槽:“老师,‘交朋友’这道题我题目看了半天,明明每个字都认识,就是不知道代码该怎么写。”也有家长问:“这道题看着像生活场景题,到底考的是啥?”如果你正在准备GESP一级,尤其是想搞懂2026年3月这次“交朋友”这道题,这篇文章就是给你写的。
“交朋友”这名字起得挺有迷惑性,实际上它是一道非常典型的一级数组应用题,核心考的是“统计每个数出现的次数,再找出现次数最多的那个数”。我带过的学生里,有人三分钟写完轻松满分,有人却在“到底应该输出哪个数”上纠结了半小时。差距不在智商,在于有没有把这几年GESP一级的高频题型吃透。这篇文章我就把这道题从读题、建模、写代码、避坑到备考,完整拆开讲一遍,不管你是一级小白还是有基础的选手,都能用得上。
1. 题目回顾:GESP一级“交朋友”到底在问什么
1.1 题面与样例说明
这道题我依据考生回忆版整理,具体描述细节可能和官方原题略有差异,但核心逻辑一致。大意是这样:
幼儿园里有n个小朋友,每个小朋友都有一个“友好值”(一个整数)。老师发现,友好值相同的小朋友更容易成为好朋友。现在想知道:在所有友好值里面,出现次数最多的是哪一个数值?如果存在多个数值出现次数一样多,请输出其中最小的那个。
输入格式:
- 第一行一个整数n,表示小朋友的人数;
- 第二行n个整数,分别表示每个小朋友的友好值。
输出格式:
- 一行一个整数,表示“出现次数最多且数值最小”的友好值。
样例输入:
text复制7
3 1 4 1 5 9 1
样例输出:
text复制1
样例解释:友好值1出现了3次,是全场最多的;3、4、5、9各出现1次。所以答案输出1。
这个题面非常友好,没有任何弯弯绕绕的陷阱,但考试时很多孩子依然会错,错的原因往往是“代码写出来了,但并列情况的处理不对”。后面我会专门讲。
1.2 这道题隐藏的3个考点
看起来是“交朋友”,其实题目背后藏了三个一级核心考点:
第一,数组的建立与访问。你要用一个数组来记录每个友好值的出现次数。很多学生第一次接触“计数数组”这个概念,觉得抽象,其实它就是一张点名册,下标是友好值,格子里存的是这个友好值出现了几次。
第二,循环遍历找最值。你要在一堆数据里找到“出现次数最多”的那一个。找最大值的模板是一级必考的基本功,单独考的时候大家都会,但套在题目场景里,有些孩子就反应不过来了。
第三,并列情况的处理。题目要求“如果多个友好值出现次数一样多,输出最小的那个”,这不是附加题,而是题目的一部分。如果你遍历时用的是大于等于,最后输出的可能就是错误答案。
1.3 考纲定位:为什么一级会考“交朋友”
GESP一级考试面向的是刚开始学编程的学生,考纲范围一般就是变量、数据类型、输入输出、分支语句、循环语句、一维数组这些基础内容。一二级不会考太复杂的算法,更多是考察“能不能把实际问题转化成程序逻辑”。
“交朋友”这道题完美踩中了一级的几大考点:它需要你定义一个输入变量n,需要用一个循环连续读入多个整数,需要用一个数组来记录频次,还需要在数组里找最值。一道题把数组、循环、分支全部串起来,属于一级里“综合题”的典型代表。
所以我一直跟学生说,GESP一级的满分并不难拿,但前提是你要把这些基础零件练成肌肉记忆。看到“交朋友”这种题,第一反应应该是“这不就是统计频次吗”,而不是在那儿纠结“交朋友到底跟我有什么关系”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思考路径:从生活场景到数组建模
2.1 从“谁会跟谁交朋友”到“统计次数”
拿到一道题,不要上来就写代码,先在草稿纸上把题目的数学本质抽出来。“友好值相同的小朋友更容易成为好朋友”,这句话翻译过来就是:我不用关心小朋友分别是谁,我只关心每个“友好值”出现了多少次。
举个例子:友好值分别是3、1、4、1、5、9、1,那1出现了3次,其他都出现了1次。谁出现次数最多?1。这个过程完全没有涉及到人和人之间的具体关系,本质就是一堆数里找众数。
你要养成一个习惯:读完题以后先问自己,这道题要维护什么信息?需要记住每个人的编号吗?不需要,因为题目只关心数值的频次。需要排序吗?不需要,顺序无所谓,统计结果不依赖先后。一旦你把这些“不需要”想清楚,思路就清晰了。
这类题的建模方式通常有两种:一种是“开一个和输入量一样大的数组,把所有数存下来再处理”,另一种是“边读边统计,根本不需要存原始数据”。对于“交朋友”这个场景,明显第二种更高效,因为频次信息已经覆盖了原始数据。
2.2 桶计数:数组下标就是那本“点名册”
统计次数最直观的方法,就是“桶计数”。你可以把数组想象成一排格子,每个格子编号从1到100(假设友好值的范围是1到100),一开始每个格子里都是0。每读到一个友好值x,就往编号为x的格子里放一个记号,相当于“记一笔”。
具体来说:
- 定义数组cnt[105]并初始化为0,cnt[x]表示友好值x出现了几次;
- 每次读到x,执行cnt[x]++;
- 全部读完以后,cnt数组里存的就是每个友好值的出现次数。
为什么数组能当点名册?因为数组支持“随机访问”,我可以直接用cnt[x]访问到下标为x的那个格子。这是数组最重要的特性,也是它比分两次遍历更高效的原因。很多孩子一开始觉得数组抽象,我就让他们把它想象成“一排带编号的储物柜”,下标是柜门上的编号,格子里存的东西就是我们要维护的数据。
2.3 找最大值的三步走:索引、次数、并列处理
统计完频次之后,下一步是在cnt数组里找出“出现次数最多”的下标。这里有个新手容易绕晕的地方:我们找的不是“数字最大”,而是“次数最多”。什么意思?友好值5只出现了1次,友好值1出现了3次,答案应该选1而不是5,因为1出现的次数多。
所以在遍历的时候,比较的对象是cnt[i]的“次数”,一旦某个i的次数比当前最大次数大,就更新最大次数,并且记录下这个i。这一步是整道题的核心。
关于并列处理,我再强调一遍:如果存在并列最多,要输出“最小”的那个。实现方法是在遍历时只用严格大于号(>)来更新答案,小于或等于的情况都不更新。因为遍历顺序是从小到大,遇到并列次数时,较小的数值已经先被记录了,后面更大的数值不会覆盖它。
这地方就是天然的分水岭:学过的人一眼就能看出门道,没学过的人会在那里反复纠结“那我到底更新不更新”。
3. 完整代码实现与逐行拆解
3.1 参考代码(C++,附注释)
下面是一份可以直接运行的C++参考代码,我用的是GESP一级考生最熟悉的写法,没有用任何超纲语法:
cpp复制#include <iostream>
using namespace std;
int main() {
int n;
cin >> n; // 读入小朋友人数
int cnt[105] = {0}; // cnt[x]表示友好值x出现的次数,初始都为0
int x;
for (int i = 0; i < n; i++) {
cin >> x; // 依次读入每个小朋友的友好值
cnt[x]++; // 友好值x的计数加1
}
int maxCnt = 0; // 当前最大出现次数
int ans = 0; // 当前出现次数最多的友好值
// 从1到100依次遍历所有可能的友好值
for (int i = 1; i <= 100; i++) {
if (cnt[i] > maxCnt) { // 只处理严格大于的情况
maxCnt = cnt[i];
ans = i;
}
}
cout << ans << endl; // 输出答案
return 0;
}
这份代码提交到GESP的评测系统里,正常情况能拿满分。但请注意:数组开多大,取决于题目给出的友好值范围。我这里假设友好值是1到100,所以开了105。如果原题范围是1到1000,就开1005,这个要根据考场上的实际题目来决定。
3.2 关键代码细节:为什么这么写
先看初始化。int cnt[105] = {0}; 这一句必须写对。如果只写int cnt[105];而不初始化,数组里面可能是乱七八糟的随机数,后面做计数时结果一定错。C++里局部数组不会自动清零,这一点跟全局数组不一样,很多新手在这栽过跟头。
再看读入循环。for (int i = 0; i < n; i++) { cin >> x; cnt[x]++; } 这段代码边读边统计,不需要用数组保存所有原始输入。可能有学生不理解:为什么不先把所有数存到一个数组里,再统计?其实也可以,但没必要。我们需要的只是频次,直接在这个过程中累加就行。这也是一级考试里比较重要的一个思想:不要存不需要的数据。
接着看找答案的循环。if (cnt[i] > maxCnt) 这里用了严格大于。为什么不是大于等于?因为在并列次数的情况下,我们需要输出更小的那个数。假设1和5都出现了3次,遍历到1时,maxCnt从0变为3,ans设为1;遍历到5时,cnt[5]等于3,和maxCnt相等,不满足“大于”,所以ans不会被改成5。最终输出1,完美符合题意。如果这里写的是大于等于,答案就会变成5,直接丢分。
最后是输出。cout << ans << endl; 注意不要多输出任何提示性文字,比如“答案是:”,评测系统只认纯数字结果,多输出任何字符都会被判错。
3.3 时间和空间复杂度分析
这道题的时间复杂度是O(n + M),其中n是小朋友人数,M是友好值的取值范围。读数据阶段要执行n次计数操作,统计答案阶段要遍历M个桶,所以总操作次数是n加M。
如果n是1000,M是100,那一共才1000多次操作,对计算机来说就是一瞬间的事。空间复杂度是O(M),因为只开了一个大小为M的计数数组。对于一级题目常见的数据范围,这个代码在时间和空间上都绰绰有余。
我建议所有考生都养成算复杂度的习惯。虽然一级考试不一定直接考复杂度分析,但你在思考“要不要排序”“要不要开大数组”的时候,复杂度思维能帮你快速做出正确判断。
4. 踩坑实录:考场里的高频错误你占了几个
4.1 数组开小导致越界
这是最典型、最致命的错误。如果题目说友好值的范围是1到100,有的学生只开到int cnt[100],那访问cnt[100]时就已经越界了。C++的数组下标从0开始,cnt[100]对应的数组至少要101个元素,也就是int cnt[101]。更稳妥的做法是开大一点,比如105甚至110。
为什么多开几个位置很重要?因为数组越界在本地运行的时候不一定报错,它可能只是悄悄覆盖了旁边变量的内存,导致结果莫名其妙地错。到了评测系统里,可能就变成“运行时错误”或者答案错误。很多学生找半天找不到原因,最后才发现就是数组开小了这一件事。
我的习惯是:只要数组大小为N,我一般开N+5或者N+10。多开的几个位置几乎不占内存,但能帮你躲掉一大批边界问题。
4.2 计数数组忘记初始化
还有一个高频错误:定义了int cnt[105];但忘记赋初值,就直接在循环里执行cnt[x]++。这样cnt数组里存的是随机值,可能一开始是负数、可能是几千几万,计数结果完全是乱的。
记住,局部数组不会自动清零。如果你希望数组所有元素初始为0,要么在定义时写int cnt[105] = {0};,要么在开头用循环手动清零。GESP一级考场上,我更推荐前者,一句话解决,简洁又不容易漏。
4.3 并列最多时“最小”没体现
这道题最大的“坑”其实就在并列处理上。很多学生找到了出现次数最多的值,但这道题加了一个限定条件:如果多个值并列最多,输出最小的那个。有些学生从头到尾用了大于等于,导致答案变成了“最大的”那个,丢掉了应该稳拿的分数。
这种丢分不是不会做,而是读题不够细。我建议你在考场上养成一个习惯:把题目里的“最”“小”“大”“如果……一样多”这些关键词圈出来,或者在草稿纸上写一个大大的提示。编程考试不只是考代码,也考读题。
4.4 编译报错让全场最慌:IDE到底怎么用
关于热搜里的“gesp考试里的编译软件指的是什么”,这里统一说一下。考场上你写的代码需要在一个集成开发环境(IDE)里编译运行,常见的包括Dev-C++、Code::Blocks这类工具,或者考试系统自带的在线编辑器。它们的作用是一样的:让你写代码、点按钮编译、在窗口里看输出结果。
我见过不少学生,代码本身是没问题的,但考场上连“编译运行”按钮都不熟悉,或者根本不知道要看编译窗口里的报错信息。比如写完了代码点运行,发现没反应,其实是编译按钮没点;又比如代码里写了全角分号,编译报错,但不会看错误提示,白白浪费几分钟。
我的建议是:备考期间别只在在线评测系统里写题,一定要在自己电脑的IDE里多敲几遍代码。练习时故意制造几个编译错误,看看编译窗口是怎么提示的,这样到了考场才不会慌。
5. GESP一级命题风格分析与备考路线
5.1 从近几年真题看一级命题趋势
GESP一级的题目,几乎年年都有这种“生活化包装”的题。比如“小猫分鱼”“消消乐”这些名字,听起来像是游戏或者童话,实际内核都是基础语法加简单逻辑。命题人把知识点藏在名字背后,考察的核心从来不是“读故事”能力,而是你能不能从故事里提取出数学和逻辑模型。
“交朋友”这道题正是这种趋势的典型代表。名字可爱、语言生活化,但你把它剥开以后,就是数组计数加最值查找。一级真正的高频考点其实很集中:
| 考点模块 | 常见出题形式 | 对应“交朋友”里的体现 |
|---|---|---|
| 输入输出 | 读入n、读入数组元素、输出结果 | 读人数、读友好值、输出答案 |
| 分支语句 | 判断条件、分类讨论 | 比较次数大小 |
| 循环语句 | 遍历数据 | 边读边统计、遍历cnt数组 |
| 一维数组 | 存储和访问多个数据 | 用cnt数组当计数桶 |
| 简单算法 | 求和、找最值、计数 | 统计频次、找出现次数最多的值 |
如果你发现这一套你已经滚瓜烂熟,那一级优秀(90分以上)问题不大。如果还有些生疏,可以针对表格里的每一项分别练习,不用急着刷难题。
5.2 把“交朋友”改成五种变式题
备考最忌讳就题论题,做完一道题要会举一反三。接下来我列几种“交朋友”变式,你可以自己试着改代码。
变式一:输出出现次数最多且数值最大的友好值。 只需要把遍历方向改成从M到1,或者把更新条件改成大于等于,就能实现“并列时选大”的效果。这个改动非常小,但能帮你理解大于和大于等于的区别。
变式二:输出出现次数第二多的友好值。 这需要用两个变量分别记录最大次数和第二大次数,同时还得维护对应的数值。难度稍微上了一个台阶,但依然在一级可控范围内。
变式三:只统计友好值为偶数的出现次数。 那在读入时加一个判断if (x % 2 == 0) cnt[x]++;就可以了。这个变式考察分支语法的组合使用。
变式四:求所有小朋友的友好值总和。 把频次统计改成累加,用sum += x即可。这个变式看起来更简单,但能帮你对比“计数”和“求和”两种数组处理的不同。
变式五:输入的是姓名而不是编号。 那就不能用数字下标做桶了,得用字符串数组或者映射。这种方式在GESP一级可能有点超纲,但你可以想一想:如果输入的是“小明 小红 小明”,怎么统计每个人出现的次数?这就为二级的内容做了铺垫。
5.3 零基础到一级的备考节奏
经常有家长问我,孩子零基础,需要准备多久去考一级?我的经验是:每周认真学2到3次,每次1小时左右,大概需要3到4个月。前提是每一次都动手敲代码,而不是只看视频、只看书。
我建议把备考分成四个阶段。第一个阶段,把IDE装好,学会新建代码文件、写一个最简单的“Hello World”、编译运行,至少花一周时间把工具用熟练。第二个阶段,系统学变量、输入输出、四则运算、if分支、for循环,这是后面所有代码的地基。第三个阶段,重点学一维数组,尤其是数组和循环的结合使用,可以做“桶计数”“找最大值”“求和”这类专项练习。第四个阶段,开始刷GESP历年真题和模拟题,每次刷完以后把错题整理出来,针对性地补漏洞。
“交朋友”这道题,其实可以作为第四个阶段的典型例题。如果你能独立、不看书、不看提示地写出代码,还知道为什么用严格大于而不是大于等于,一级的数组部分基本就稳了。
6. 常见问题速查与考场建议
6.1 高频问题速查表
我整理了学生之前问得最多、错得最多的问题,做成一个速查表,你可以备考时对照检查。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 程序运行崩溃或无输出 | 数组越界 | 数组大小按数据范围多开5到10个 |
| 输出结果完全不对 | 计数数组未初始化 | 定义时写成int cnt[105] = {0}; |
| 并列次数时输出了较大的数 | 更新条件写成了大于等于 | 改成严格大于> |
| 样例能输出,提交判错 | 输出格式不符合要求 | 只输出结果数字,不加提示文字 |
| 编译报错,找不到问题 | 变量名重复或中英文符号混用 | 检查所有分号、括号是否为英文半角 |
| 本地运行正常,提交时编译失败 | 使用了考试环境不支持的语法 | 用标准C++语法,避免编译器扩展特性 |
这张表你在考前最后一天晚上看一遍,比刷三套新题都有用。
6.2 考场上的几个实用建议
最后再分享几个考场上的实际经验。第一个,考试时先做自己有把握的题,不要在一道题上死磕超过20分钟。GESP一级的题量不大,但有些孩子会因为前面纠结太久,导致后面会做的题时间不够。
第二个,提交之前一定要在本地把样例数据测一遍,同时再自己造几组特殊数据测一测。比如n等于1的时候我的程序输出什么?所有友好值都一样的时候输出什么?并列最多的时候是不是选了最小的?这种“边界测试”是拿高分的关键。
第三个,多花30秒读题非常值得。我见过太多学生因为没看到“如果并列输出最小”这几个字而丢分,那真的太可惜了。你完全可以先花两分钟把题目多读一遍,把关键条件圈出来,再开始动手,这比写完之后反复纠结要高效得多。
第四个,也是最重要的一个:考场上的心态要放平。“交朋友”这种题,虽然名字可爱,但本质上就是老师平时练过无数次的“统计频次”题。只要你平时踏踏实实敲过代码,把数组、循环、分支这些基本功练到位了,看到题目稍微分析一下,思路自然就出来了。
