尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

C语言数组统计数字出现次数:下标映射法详解

发布时间:2026/9/26 22:27:25

资讯中心
01
ARTICLE

C语言数组统计数字出现次数:下标映射法详解

C语言数组统计数字出现次数:下标映射法详解
1. 这道题到底在考什么先看懂数字和整数的区别先说个我辅导学生时遇到的典型场面。很多人拿到求一批整数中出现最多的数字这个题目第一反应是把这一批整数挨个比较看看哪个数出现的次数最多。这个思路错得离谱——题目说的是数字digit不是数number。数字指的是0到9这十个字符整数指的是完整的数值本身。124和421是两个不同的整数但它们都包含数字1、2、4。题目要的是在这一批整数的十进制表示里0到9这十个数字哪个出现得最频繁。这是何钦铭、颜晖版《C语言程序设计第四版》第七章数组部分非常典型的一道习题。放在第七章出题意图很明显这一章的主线就是批量数据的组织与处理而数组就是C语言里组织批量数据最基础的手段。这道题表面上是在练循环和取余取模实际上是在训练一个更重要的思维——用下标直接对应数据本身用数组元素的值记录统计结果。1.1 拆分题目输入、处理、输出分别要做什么把题目拆开它其实有三个明确的任务段输入段读入整数个数n再逐个读入n个整数。这里有个隐藏细节——题目给的是一批整数不是一批数字所以输入的是整型数据不能用字符方式处理。处理段对每个整数拆解它的每一位数字统计各数字0~9出现的次数。拆位用循环和取余%取整用整数除法/。输出段找出出现次数最多的那个数字把数字本身和它的出现次数打印出来。注意如果有多个数字并列最多教材原题一般要求输出最小的那个或者按题目具体要求处理。这个三段式结构是所有编程题的通用框架。学生最容易栽的地方在第二段不会把统计十个数字出现次数和数组联系起来。其实思路非常直接——定义一个int digit[10]数组下标0到9恰好对应十个数字每次拆出一个数字d就执行digit[d]。下标即数据值即次数这就是数组最漂亮的用法。1.2 教材第七章为什么把这道题放在这里第七章讲数组前半部分讲一维数组的定义、初始化和引用后半部分讲排序、查找、插入删除这些经典操作。这道题并不涉及排序和查找但它把数组用在了另一个高频场景——计数统计。计数统计看起来简单却是很多复杂算法的基石比如桶排序的思想、哈希表最原始的雏形、甚至图像直方图统计本质上都是数据值映射到下标下标位置累加。所以这道题的教学价值不在难而在巧。它让孩子第一次体会到数组不只是拿来存一堆数据然后遍历的容器它还可以是一个天然的映射表。这种思维转换比会写一百行循环都重要。2. 核心算法拆解为什么下标即数字是最优解直接上核心逻辑。假定已经读入了n个整数现在要统计每个数字出现多少次。最朴素的错误做法是把每个整数转成字符串然后逐个字符去判断是0还是1……一直到9写十个if或者一个switch。这种写法不是不行但代码冗长不说思维上也绕了远路。更聪明的做法就是定义一个长度为10的数组用数字本身做下标int digit[10] {0}; // 下标0~9分别对应数字0~9初值全为0 int x; while (x ! 0) { digit[x % 10]; // 取出个位数字对应下标加1 x x / 10; // 去掉个位 }这段代码的精髓只有两行。x % 10拿到当前最低位的数字比如123 % 10得到3这个3直接作为下标去访问digit[3]x / 10把最低位砍掉123变成12下次循环再取出的就是原来的十位2。循环往复直到x变成0为止。整个过程既不涉及字符串转换也不涉及浮点运算全部是整数运算效率和可读性双高。2.1 用宿舍楼分房间来理解计数数组为了让学生彻底理解这个思路我上课时经常打一个比方。假设你要统计一栋宿舍楼里从0号到9号十个房间各住了多少人最笨的办法是拿个本子挨个记0号房住了3人1号房住了2人……但更聪明的做法是在楼下放十个计数器每个房间门口挂一个。每来一个人住进3号房你就把3号房门口那个计数器的数字加1。等所有人入住完毕扫一眼十个计数器就完事了。这里的房间号就是数字本身计数器就是数组元素digit[3]而有人入住这个动作就是拆位拆出了一个3。C语言里数组下标从0开始恰好和数字0对齐这是语言给你的天然便利不用白不用。2.2 完整的可运行代码光讲思路不够直接把完整代码贴出来照着敲一遍就能跑#include stdio.h int main(void) { int n, i, x, d; int digit[10] {0}; // 统计0~9各数字出现的次数 int max 0; // 记录最大出现次数 printf(Input n:); scanf(%d, n); printf(Input %d integers:, n); for (i 0; i n; i) { scanf(%d, x); if (x 0) { // 处理负数先取绝对值再拆位 x -x; } if (x 0) { // 特殊处理整数0本身包含一个数字0 digit[0]; } while (x ! 0) { d x % 10; digit[d]; x x / 10; } } // 找出出现次数最多的数字若并列取数字最小的 for (i 1; i 10; i) { if (digit[i] digit[max]) { max i; } } printf(出现最多的数字是%d出现次数为%d次\n, max, digit[max]); return 0; }这个版本是我根据教材习题要求做了完整处理的版本比教材上某些简化的示例多了两个关键细节负数处理和0的处理。这两个细节恰恰是初学者最容易踩的坑下面专门展开说。2.3 为什么初始化必须写{0}而不是省略数组定义int digit[10] {0};这个初始化不是可写可不写的装饰。C语言规定局部数组如果不初始化它的元素值是随机的取决于栈上残留的数据。如果不写 {0}你后面执行digit[d]操作的其实是一个不确定的初值统计结果必然出错。很多教材在讲数组初始化时会说可对全部元素赋初值但实践中最稳妥的做法就是要么写{0}让全部元素归零要么用循环显式赋值for (i 0; i 10; i) digit[i] 0;。我见过太多学生把初始化漏掉然后统计结果一会儿大一会儿小找半天bug找不到原因最后发现是数组初值没归零。这个坑几乎每个初学者都会踩一次。3. 边界条件与常见坑负数、0和输入缓冲区的那些事3.1 负数拆位前必须先取绝对值教材原文并没有明确说明输入是否包含负数。如果输入是负数比如-123直接while (x ! 0)循环会怎么走-123 % 10在C语言里的结果是-3C89标准允许结果为负C99以后规定向零取整所以-123 % 10 -3这样digit[-3]就会数组越界程序可能直接崩溃或者悄无声息地写坏其他内存数据。更极端的情况如果x -2147483648int类型的最小值对int取绝对值也是未定义行为这是另一个坑不过教材题目一般不会考这么深。安全的做法非常朴素读入x之后先判断if (x 0) x -x;把负数转成正数再拆位。这个操作虽然简单但如果不加程序行为就是未定义的。数组越界是C语言里最隐蔽的杀手有时候当场不报错等程序运行到某个临界点才崩排查起来让人崩溃。3.2 整数0循环一次都不会执行但它包含一个数字0这是最容易被忽视的边界情况。如果输入中出现整数0那么while (x ! 0)这个循环条件从一开始就是假循环体一次都不会执行。可是0这个数在十进制表示里确实包含一个数字0它应该被统计进去。所以必须在循环前单独判断if (x 0) digit[0];。我之所以专门强调这一点是因为很多参考书的示例代码根本不处理0导致如果某次测试数据里恰好有0统计结果就少了一个0的计数。这种错误在平时练习时很难发现因为大多数人的测试数据都是正整数。一旦上了OJ或者考试系统边界测试用例必然包含0一测就挂。做题务必养成边界条件优先的习惯——输入的最小值、最大值、等于0、负数这些极端情况都必须过一遍脑子。3.3 scanf连续读入的换行符陷阱这段代码里用了两次scanf一次读n一次在循环里逐个读整数。scanf在读%d时有一个经典行为它会跳过前面的空白字符空格、换行、制表符所以即使你输入的时候每敲一个整数按一次回车程序也能正常逐个读到值。这个行为对本题而言是安全的不需要额外处理。但要注意一个反例如果之后有人图省事用了scanf(%c, ch)读字符那换行符就会变成\n被读进去这是另一个经典bug。本题用不到字符输入不过这个知识要记着第七章后面的习题比如读字符串统计字符频次就会碰到。3.4 我建议的测试数据组合调试这类统计程序不要只用一组数据。我每次给学生讲完这道题都会让他们至少跑三组用例测试场景输入示例期望结果常规正整数5: 123 456 789 123 456数字1、2、3、4、5、6各出现2次并列最多取最小数字1包含03: 0 10 100数字0出现4次最多包含负数4: -111 222 -111 999数字1出现6次最多这三组覆盖了最核心的三个逻辑分支普通拆位循环、x0的特判、负数取绝对值。我见过很多学生第一组跑对了就急着交作业结果第二组、第三组全崩。测试不是走过场编程的严谨性就是从设计测试用例开始的。4. 从这道题延伸出去数组计数的三种变体与真实场景4.1 变体一统计的不是数字而是数字段区间统计统计单个数字太简单了教材后续习题通常会升级给一批成绩统计各分数段的人数90~100、80~89、70~79等。这时候数组的下标就不直接等于分数了而是下标i对应第i个分数段。比如用score[0]存不及格人数、score[1]存60~69分人数……核心代码就一句话int level x / 10; // 84 / 10 8对应下标8 if (level 9) level 9; // 满分100要归到最高档 if (level 0) level 0; // 异常数据粗筛 count[level];你看这跟本题的下标即数字是同一个思想区别只在于从一位数字变成了一个区间映射。能搞懂这道题分数段统计就是分分钟的事。4.2 变体二从批量整数变成单个大整数还有一个常见变形输入一个很长的整数比如身份证号、银行卡号然后统计里面每个数字出现的次数。这时候就不能用int去读了因为位数超长。正确的做法是用字符串读入char str[100]; scanf(%s, str); int digit[10] {0}; for (int i 0; str[i] ! \\0; i) { digit[str[i] - 0]; }注意str[i] - 0这个操作——字符0到9的ASCII码是连续的减去字符0的ASCII码就能把字符型数字转换成整型数字。这个技巧在本题目中不需要我们直接用x % 10得到的就是整型数字但很多后续习题会用到早点掌握不吃亏。4.3 真实场景这思想其实无处不在别觉得统计数字出现次数是典型的作业题现实中它的影子到处都是。比如数据压缩算法里的频次统计——霍夫曼编码第一步就是统计每个字符的出现频率核心代码和本题一模一样只不过把数组长度从10换成了256ASCII码范围。再比如电商平台分析用户购买记录要找出被加入购物车次数最多的商品编号思路也是把商品ID映射到数组下标ID对应位置自增。工业界的实时日志分析、关键词热度排行底层逻辑都能看到这个桶计数的影子。每次看到学生说这题有什么实际用途我都很想告诉他们你以后接触哈希表、字典、Map这些数据结构时会发现它们都在解决同一个问题——怎么把查询数据的复杂度降到接近O(1)。而数组直接通过下标访问本身就是O(1)查询的极致形态。这道题就是你第一次亲手搭起的最简版哈希表。4.4 多数字并列最多时的输出策略本题还有一个容易被忽略的输出细节如果0到9中有多个数字出现次数并列最多应该输出什么根据多数教材习题的约定是输出最小的那个数字。我的代码里用了一个小技巧int max 0; // 先假定数字0出现次数最多 for (i 1; i 10; i) { if (digit[i] digit[max]) { max i; } }注意这里用的是而不是。当两个数字出现次数相同时会保留下标较小的那一个因为下标循环从小到大遍历后来者只有在严格大于当前最大次数时才会替换天然满足输出最小数字的要求。如果题目改成输出所有并列最多的数字那就要在找完最大值之后再遍历一遍数组把所有digit[i] digit[max]的i都打印出来。细节虽小但不同题目要求不同读题时务必看仔细。5. 从教材到实战代码风格的三个进阶建议5.1 用一个符号常量定义数组长度别写魔法数字如果你留意过我给的代码会发现数组长度直接写了10循环也直接用了9。这种写法对这道题来说没问题因为0~9就是十个数字数字的个数是天然的常量。但从专业代码风格角度更推荐写成#define DIGIT_COUNT 10 int digit[DIGIT_COUNT] {0};为什么因为数组长度和循环边界在程序中多处出现一旦将来你要统计十六进制数字0~15共16个、或者统计ASCII可打印字符95个只需要改一行常量定义。直接写10写9改起来就要全局搜索替换漏改一处就是隐蔽bug。教材上的代码为了教学简洁通常直接写数字但自己练习时养成用宏定义的习惯对后续做项目有百利无一害。5.2 把拆位统计的逻辑封装成函数这道题的main函数里放了两层循环加一个查找最大值的过程代码不长但逻辑其实可以分成更清晰的三块读入数据、统计、找最大值。实战中我更习惯把统计这一块抽成函数void count_digits(int x, int digit[]) { if (x 0) x -x; if (x 0) { digit[0]; return; } while (x ! 0) { digit[x % 10]; x / 10; } }函数化之后main函数变得简洁易读而且这个count_digits函数具有通用性——以后任何地方需要统计某个整数的数位直接调用即可。这是初学阶段就要开始建立的模块化意识函数不是把代码拆碎而是把逻辑分层。5.3 用调试器看数组值的变化理解代码的最好方式如果条件允许强烈建议在IDE里对这段代码下断点一行一行单步执行同时打开监视窗口观察digit数组的变化。比如输入整数123你会清晰地看到digit[3]先从0变1然后digit[2]从0变1最后digit[1]从0变1。这个过程比看任何讲解都直观它能把拆位和数组计数这两个抽象概念变得无比具体。这里我基于实际教学经验补充一句大多数学生能看懂代码但只有亲手单步跟踪过一遍才算真正会了这道题。看书和写字是两回事写字和真正理解又是两回事。6. 关于这道题我个人的备课心得最后聊几句我自己的看法。这道题作为第七章的习题难度定级不高但它的信息密度很大涉及循环嵌套、数组初始化、取余取整运算、边界条件判断、极值查找还有一点点的输出策略设计。我在实际教学里会把这道题当作数组真正入门的门槛——前面那些一维数组求均值、求最大值的题目本质上还是把数组当存放数据的容器但到了这道题数组第一次变成了参与运算的核心数据结构下标第一次有了业务含义。所以我也建议正在学C语言的读者做完这道题别急着往后翻自己试着做三个变形——统计一段英文里各字母出现次数、统计一批人的年龄区间分布、模拟掷骰子统计各点数频率。这三个变体如果都能独立完成第七章数组这部分的基础就算真正打牢了。编程不是刷完题目就结束的事把一道经典题吃透比潦草地做十道题更有收获。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。