刚学编程的时候十个人里有九个都绕不过这种题给你一串数再给一个指定数字让你数一数这串数里有几个跟它一样。我记得当年在题库里做到“12:与指定数字相同的数的个数”这道题时第一反应是“这有什么好做的”真写起来才发现代码不过十来行里面能翻车的点却不少。这道题看着不起眼但它几乎是所有“序列处理”类题目的地基——循环、判断、计数、数组遍历全占了后面你遇到统计词频、筛选重复元素、分析数据分布底层逻辑多多少少都和它沾边。这篇文章就把这道题从头到尾拆一遍把不同写法、容易踩的坑、以及从它延伸出去的思路都聊透。不管你是刚看完语法还在找手感的新手还是准备各类上机考试想夯实基础的选手这篇都能给你一点实在的东西。1. 题目拆解与核心解决思路1.1 这道题到底在让你做什么先把题目翻译成人话。输入大概长这样第一行给一个整数N表示接下来有N个整数第二行是这N个数本身第三行再给一个整数m。你要做的就是在这N个数里面找出所有等于m的数然后输出它们的个数。举个例子N是5第二行是“3 1 4 1 5”第三行m是1那么答案就是2因为这一串数里出现了两次1。我习惯把这类问题类比成一个非常生活的场景。你有一张全班同学的名单想统计姓“王”的同学有几位那你会怎么做肯定是一个一个看过去遇到姓“王”的就拿笔记一道。这个动作就是遍历那个“拿笔记一道”的动作就是计数。程序里所谓的“循环”和“计数器”其实就是把你这个肉眼排查的过程机械化、自动化。理解了这个类比题目的本质就清楚了遍历每一个元素将每个元素和目标值做一次比较相等就让计数器加一遍历结束后输出计数器。这中间包含了三个最基础的程序结构循环结构负责把每个数过一遍、选择结构负责判断相不相等、顺序结构负责从上到下按步骤执行。有人说这道题是“入门题中的入门题”一点都不夸张它把编程里最核心的三个控制流全串起来了。1.2 为什么说它是所有序列统计题的起点很多初学者容易忽视这道题的价值觉得它太简单。但你看凡是涉及“在一堆数据里找目标”“统计某类元素的数量”“判断某个值是否存在”的问题本质上都和这道题同构。你今天写的这个循环加判断明天换一层皮就是统计一篇文章里某个单词出现的次数再换一层皮就是统计用户行为日志里某个事件发生的频率再复杂一点配合上哈希表又能解决“找出数组里出现次数最多的元素”这类经典问题。所以这道题最终要帮你建立的不是记住某段代码而是一个思维定式面对序列数据时用遍历去访问每个元素用条件判断去筛选目标用变量去累计结果。这三板斧用得熟了后面学什么排序、查找、动态规划都会觉得顺畅很多。我在带新人做项目的时候经常看到有些人一上来就抱着复杂框架啃但让他写个统计接口每分钟被调用多少次的脚本反而卡壳。原因就是最底层的“遍历判断计数”思维不够扎实。这道入门题恰恰就是打磨这种底层思维最好的磨刀石。2. 几种典型写法与关键细节对比2.1 教科书式先把数据读进数组再遍历统计最常见的写法也是大多数教材里给出的思路是先开一个数组把N个数存下来然后再开一个循环逐个比较。C语言代码大概是这样的#include stdio.h int main() { int n, m, count 0; scanf(%d, n); int a[1000]; // 按题目要求开足够大的数组 for (int i 0; i n; i) { scanf(%d, a[i]); } scanf(%d, m); for (int i 0; i n; i) { if (a[i] m) { count; } } printf(%d\n, count); return 0; }这种写法的好处是结构清晰读入和统计两个阶段分得很开符合人脑一步一步处理问题的直觉。坏处是它需要一块额外的内存来存放这N个数如果N特别大比如几百万甚至上亿单纯为统计一个数字的次数就占这么大内存有点不划算。不过对入门阶段来说这个写法最稳妥也最容易扩展。因为数据都存在数组里后面你想做二次遍历、三次遍历——比如先统计等于m的个数再统计大于m的个数——都很方便。我当年考试的时候就爱用这种写法因为它稳不容易在思路上出岔子。2.2 更省事的做法边读入边统计如果只是单纯统计个数其实根本不需要把数据存下来。原因很简单统计结果只和“当前读到的数是不是等于m”有关和之前之后的其他数一概无关。所以完全可以每读进来一个数当场判断、当场计数读完即弃。C语言代码可以这样写#include stdio.h int main() { int n, m, x, count 0; scanf(%d, n); scanf(%d, m); for (int i 0; i n; i) { scanf(%d, x); if (x m) { count; } } printf(%d\n, count); return 0; }这个写法在内存上几乎是零开销不管N有多大我用到的变量就固定那几个。时间复杂度也很干净读入一遍、比较一遍整个过程是O(N)。很多在线评测系统的输入量是很大的这种“流式处理”的思路就能派上用场。不过要注意这个写法成立的前提是m的读取必须在循环读入N个数之前完成。如果你按照“先读入N个数、再读入m”的输入顺序那这个方案就要调整成“先开数组存数”或者“先读取m再在循环里边读边比”。顺序错了代码直接跑挂。2.3 Python版一行代码解决战斗如果你用的是Python那这道题还能玩出更花的操作。Python的列表自带一个count方法可以直接统计元素出现次数n int(input()) nums list(map(int, input().split())) m int(input()) print(nums.count(m))核心输出就直接用nums.count(m)一句话搞定。甚至更极端一点可以连着写input() nums list(map(int, input().split())) m int(input()) print(nums.count(m))第一行输入N只用来占位因为Python的列表不限制长度根本不需要事先知道有多少个数。这个写法用来刷题很爽但如果是为了练基本功我还是建议先把循环遍历的方式写熟。因为list.count在底层帮你做掉的事情恰恰是你需要反复琢磨的遍历与比较逻辑。工具可以让你走得快但如果不知道工具底下在干什么遇到工具不灵的场景就会抓瞎。比如数据量极大、内存装不下的时候Python这门语言大概率就不合适了那时候你需要换用流式处理工具或者其他大数据框架但它们的设计思想依旧没跳出“遍历、判断、计数”这三个动作。2.4 代码细节里藏着的门道不管用哪种写法有几个细节最容易出问题先说两个最典型的。第一个是计数变量的初始化。count 0这一行很多人会忘或者初始化放错了位置。C语言的局部变量如果不初始化值是不确定的可能是0也可能是某个残留值最后输出的结果就会莫名其妙多出一截。正确的做法是在进入统计循环之前把count初始化成0。如果在循环里面初始化每轮循环都重置成0那统计结果永远只有最后一个数字的判断结果。第二个是数组开多大的问题。按题目要求来一般给N的范围比如N ≤ 1000或者N ≤ 100000那就开个刚好够用的数组。开小了会越界程序可能直接崩溃开太大倒是不至于报错但浪费内存。竞赛环境里内存限制通常很严格比如256MB你要是为了小数据开个1亿的int数组直接就内存超限了。另外还有个小习惯我建议从入门就养成变量名尽量有意义。n、m、x、count这类单字母变量写起来快但可读性差。等到代码量大了或者需要团队协作的时候count写成matchCount、n写成totalCount别人读起来轻松你自己隔几天回头看也没那么费劲。这一条在所有编程场景里都适用不限于这道题。3. 从题目到代码的完整推演过程3.1 把自然语言翻译成程序语言很多新手卡壳的原因不是不会写代码而是不知道怎么写。其实有一个笨办法非常管用先把要做的事用中文一步步写下来然后再把每一步“翻译”成代码。拿这道题来举例。我如果要实现“统计一串数中等于m的个数”中文步骤是读入一共有多少个整数N。依次读入这N个整数。读入指定的目标数字m。准备一个计数器初始值为0。从第一个数开始依次检查每个数是否等于m如果等于计数器加1。输出计数器的值。把这六步对照着代码看每一步几乎都能一一对应上。第1步对应scanf读入n第2步对应循环里的scanf第5步对应if(a[i] m)判断和count第6步对应printf输出。这种“中文伪代码→真实代码”的转化方式是解决一切编程题目的基本方法。题目再难拆到最后也无非是一堆这样的基础步骤按逻辑组合在一起。我做项目时写复杂逻辑也会这样操作先写注释再在注释下面填真正的代码。注释就是给接下来的自己留的索引也是逼自己把思路理清楚的手段。所以建议新手不要怕麻烦拿到题目先别急着敲键盘拿张纸或者直接在编辑器里写上伪代码再开始写正式代码。整个过程看着慢实际比一遍遍试错快得多。3.2 测试用例怎么设计才靠谱代码写完之后很多人运行一次示例输入看到输出对了就觉得万事大吉。这个习惯很危险因为示例输入往往是最普通的情况覆盖不了边界场景。以这道题为例我通常会准备这么几个测试用例测试场景输入期望输出普通情况5 / 3 1 4 1 5 / 12一个都没匹配4 / 2 3 4 5 / 90全部匹配3 / 7 7 7 / 73只有一个数且匹配1 / 6 / 61只有一个数且不匹配1 / 6 / 00目标数字不在输入中4 / -1 0 8 100 / 420第一行是标准示例第二、三行测的是两种极端完全没有匹配和全部匹配。最后几行测的是最小规模数据。这些边界情况最容易暴露问题比如数组越界、循环次数错误、计数器初始值错误等。如果你能把上面这几个用例全部跑通这道题基本就稳了。我见过不少人在调试时喜欢直接在程序里加打印语句把每次循环拿到的值和count的值打印出来看这个做法对这类小程序是有效的而且是很好的调试习惯。不过打印语句只能帮助定位问题真正解决问题的关键还是回到逻辑上把循环边界和判断条件理清。程序打印的结果是表象逻辑才是根源。3.3 进阶一点的玩法一次统计多个目标数字如果题目稍微变一下要求同时统计m1、m2、m3……多个数字分别出现了多少次你当然可以循环n次跑n遍统计每个目标数字都遍历一次数组。但如果数组很大目标数字也很多那这个方案的复杂度就是O(N×M)非常浪费。更好的做法是用一个桶目标值→次数来记录遍历一遍数组每遇到一个数就把它对应的桶加1。这个思路就是频率统计也是哈希表的雏形。举个例子C语言里如果知道数字的范围可以直接用一个数组充当桶#include stdio.h int main() { int n, x; int freq[1001] {0}; // 假设数字范围为0-1000 scanf(%d, n); for (int i 0; i n; i) { scanf(%d, x); freq[x]; } int q; scanf(%d, q); // 要查询几个目标数 while (q--) { int target; scanf(%d, target); printf(%d\n, freq[target]); } return 0; }这样一来预处理一次O(N)之后每次查询只要O(1)一个freq[target]直接拿到答案效率碾压“每次查询都遍历一遍数组”的做法。别小看这个变化所有关于“高频词统计”“热门商品排行”“用户活跃度分布”的应用核心都是这套桶计数的思想。等你学哈希表的时候会发现思想完全一样只是把“桶数组”换成了更灵活的哈希结构而已。这个进阶版本我不是让你现在就完全理解透而是想告诉你一道看起来平平无奇的入门题往深了挖也能挖出生动的算法思维。学习的过程中多问一句“如果情况变了怎么办”是最快的成长方式。4. 常见问题与排查技巧实录4.1 新手最容易踩的四个坑自动评测系统里跑这道题最常见的错误无非这么几类。第一类是编译错误。常见原因是数组越界或者写成了。尤其是这个等号问题几乎所有初学者都犯过。if (a[i] m)在C语言里不是判断相等而是把m赋值给a[i]然后判断a[i]是否为非零值逻辑完全跑偏而且编译器顶多给个警告不一定会报错调试起来特别隐蔽。当年我在一台OJ上帮人看代码一堆人都是死在这一个“”。第二类是运行时崩溃或数组越界。数组开小了或者循环里把i n写成了i n最后会多访问一个下标读取到数组外面的垃圾数据运气不好直接段错误。排查方法很简单把循环里printf打印一下i和a[i]肉眼就能看出哪儿不对。第三类是输出格式错误。题目要求输出结果后换行很多人写printf(%d, count)少了\n在多数OJ上会判Presentation Error。格式看着是小问题但在自动评测系统里一样会扣分所以养成“输出必带换行”的习惯比较好。第四类是逻辑条件写反了。比如统计的是“不等于m”的个数或者判断的时候用了a[i] m然后数了一堆别的东西。这种问题光看代码有时候很难发现最有效的办法就是代入一个小数据人肉执行一遍把数组里的每个数和目标值逐一去比很快就能发现自己代码里的判断条件跟题目要求是不是一致。4.2 调试这类题目时我常用的三板斧多年下来我一碰到这种“统计个数”的题目出错通常就按下面三步走基本都能快速定位问题。第一板斧检查输入输出顺序。先把样例输入贴进去把程序跑一遍如果输出不对先别急着改逻辑。看一遍scanf或者input的顺序和题目要求的输入格式比对一下。这里栽跟头的概率非常高尤其是m的位置有些题m在第二行有些在最后一个不小心就把变量对应的输入错位了。第二板斧在关键节点加打印。在循环开始前打印一下n和m的值在循环内部打印一下当前遍历到的数字和count的实时值。比如你发现打印到第某个元素的时候count突然多加了一次那问题大概率出在那一次的判断条件或者输入读取上。第三板斧用最小的数据手动模拟。把N设为1甚至设为0如果题目允许把输入压缩到最小一步步跟踪代码的执行流程。程序越大、数据越多越难看清楚越小的数据越能暴露本质。这个方法不仅适用于这道题几乎所有算法题调试都能用上。4.3 一个容易忽略的输出细节多组输入的情况有些题会改个形式一次性给多组测试数据每组的格式都和单组时一模一样直到遇到某个标志位才结束。这时候如果你还按单组的逻辑写只能处理第一组就退出了。正确的做法是套一层大循环把每一组的处理逻辑包进去读到一个特定值比如文件结束符EOF就退出。在C语言里这种多组输入的经典写法是while (scanf(%d, n) ! EOF) { // 处理这一组数据 }在Python里则是用异常捕获或者sys.stdin来循环读。这是一道入门题可能出现的变形版本了解了它你以后再碰到“多组测试数据”的题目就不会慌了。总的来说做题的时候一定要养成细读题目的习惯搞清楚是单组输入还是多组输入这比急着写代码重要得多。5. 从这道入门题延伸出来的一些思考5.1 “统计个数”其实是频率分析的雏形前面提到过桶计数。其实“统计与指定数字相同的数的个数”这个问题可以自然地推广成“统计每一个不同数字出现的次数”。前者只关心一个目标值后者关心所有值。后者就是经典的频率统计问题。在真实世界里频率统计无处不在。搜索引擎统计热门关键词电商平台统计热销商品社交平台统计话题讨论量背后都是同一个套路把数据映射到不同的“桶”里对应的桶计数加一。只不过真实场景的数据量更大、种类更多所以桶换成了更高级的哈希表、数据库索引或者分布式计算框架。但底层的逻辑依然是你在这道题里练到的“遍历判断计数”。我觉得这就是入门题最大的价值——让你在十行代码里看见整个领域的核心思维。你现在不会觉得自己在写什么了不起的算法但几年后回头再看会发现所有的复杂系统都是由这些不起眼的基本操作组合出来的。5.2 类似变体如果要求输出第一个匹配的位置呢把题目稍微改一改给定一串数和目标值m不仅要判断有没有还要输出m第一次出现的位置下标。这就从“统计个数”变成了“查找位置”逻辑上仍然很基础但思路要稍微变一下。你不能只用一个计数器了你需要一个“是否已经找到”的标志或者找到一个就break跳出循环。再换个方向给定一串数输出这串数里出现次数最多的那个数。这就又回到了频率统计。如果数字范围很大甚至可能是负数、浮点数怎么办那就需要哈希表或者先排序再统计。每多一个条件题目的难度和所需的数据结构就往上涨一截但最内核的循环遍历一直没有变。我建议学有余力的读者每做完一道入门题都试着给自己出几个变体或者去OJ上找找同类型但多绕一步的题目做做。这种“举一反三”的训练方式对提升编程思维特别有效比闷头刷一百道同类题强得多。5.3 我的一点个人体会刷了这么多年题写过无数代码我越来越觉得“12:与指定数字相同的数的个数”这种题目真正的门槛不在于代码本身而在于你能不能把一个现实问题数一数有几个跟指定数字一样用一个精确的、机器可执行的方式表达出来。这一步叫“抽象”也叫“建模”。题目越简单建模的过程越容易被忽略但恰恰是初学者最需要刻意练习的东西。当你有一天突然发现自己拿到题目后脑子里不再是一团浆糊而是能够自然而然地浮现出“哦就是遍历一遍然后统计嘛”的清晰思路时说明你已经迈过了一道无形的坎。从那以后程序对你来说不再是一个个孤立的语法点而是一块块可以随意拼接的积木。很多过来人说编程入门难难的其实就是这个从“看不懂”到“有思路”的临界点。而多写这种基础题就是帮你快速摸到并跨越这个临界点的捷径。最后分享一个我的小习惯写完这道题之后我通常会把数组换成链表结构再实现一遍也会尝试用递归的方式去统计个数。这些操作看起来是折腾但每折腾一次你对“数据怎么组织”“程序怎么控制”的理解就会再深一层。编程这种事说到底没有太多捷径就是在这些基础的题目里一遍遍打磨直到把内功练成肌肉记忆以后看到任何复杂问题都能快速拆解成自己熟悉的那些基本招式。