C语言学习笔记20260620-数字筛选算法:排除4的倍数与含4数字
一、学习目标
掌握在C语言中处理"数字特征筛选"类问题的多种编程思路,包括数学取模运算、函数封装思想以及字符串处理技巧。通过对比三种不同的实现方法,深入理解底层算法逻辑与代码可读性之间的权衡。
二、问题拆解与核心逻辑
本题要求输出 1 到 n 之间既不是 4 的倍数,也不包含数字 4 的整数。解决该问题需要处理两个独立的判断条件:
- 数学条件 :判断当前数字
i是否能被 4 整除(i % 4 == 0)。 - 数位条件 :判断当前数字
i的十进制表示中是否包含数字 4。
只要满足任意一个条件,该数字就需要被过滤掉。
三、方法一:临时变量拆数法(数学取模)
3.1 核心思路
利用数学中的"取模(%)"和"整除(/)"运算,从低位到高位逐位拆解数字,检查每一位是否等于 4。
3.2 代码解析
c
for (int i = 1; i <= n; i++)
{
if (i % 4 == 0) continue; // 提前过滤掉4的倍数
int tmp = i;
int has4 = 0; // 标记变量
while (tmp > 0)
{
if (tmp % 10 == 4) // 取出当前最低位判断
{
has4 = 1;
break; // 找到4立即跳出循环,提高效率
}
tmp /= 10; // 去掉最低位,准备判断下一位
}
if (has4 == 0)
printf("%d\n", i);
}
3.3 关键细节与优化
- 保护原始数据 :使用临时变量
tmp来承接i的值进行拆解,避免直接修改循环变量i导致死循环或逻辑错误。 - 循环终止条件 :
while (tmp > 0)是拆数法的核心。当tmp变为 0 时,说明所有位数都已检查完毕。如果错误地写成tmp >= 0,当tmp为 0 时会陷入死循环。 - 提前终止 :一旦发现某一位是 4,立即使用
break跳出循环,无需继续检查剩余的高位。
四、方法二:封装判断函数(模块化思想)
4.1 核心思路
将"判断是否包含数字4"这一独立逻辑抽取为一个单独的函数 containFour,使主函数 main 的逻辑更加清晰。
4.2 代码解析
c
// 判断x是否包含数字4,含4返回1,不含返回0
int containFour(int x)
{
while (x > 0)
{
if (x % 10 == 4)
return 1; // 找到4直接返回真
x /= 10;
}
return 0; // 遍历完所有位都没找到4,返回假
}
// 主函数逻辑极其简洁
for (int i = 1; i <= n; i++)
{
// 两个条件满足任意一个就跳过
if (i % 4 == 0 || containFour(i))
continue;
printf("%d\n", i);
}
4.3 优势分析
- 可读性强 :主循环中的
if条件读起来就像自然语言(如果是4的倍数 或 包含4,则跳过)。 - 复用性高:如果后续题目要求判断是否包含数字 7,只需修改或新增一个函数,无需改动主循环逻辑。
- 逻辑解耦:将复杂的拆数逻辑隐藏在函数内部,降低了主函数的认知负担。
五、方法三:字符串处理法(类型转换)
5.1 核心思路
将数字转换为字符串,把"数位检查"问题转化为"字符查找"问题。利用 sprintf 进行数字到字符串的转换,再遍历字符串查找字符 '4'。
5.2 代码解析
c
char buf[20]; // 存放数字转成的字符串
for (int i = 1; i <= n; i++)
{
if (i % 4 == 0) continue;
sprintf(buf, "%d", i); // 将整数 i 格式化输出到字符数组 buf 中
int has4 = 0;
for (int j = 0; buf[j] != '\0'; j++)
{
if (buf[j] == '4') // 注意这里比较的是字符 '4'
{
has4 = 1;
break;
}
}
if (!has4)
printf("%d\n", i);
}
5.3 适用场景与局限
- 适用场景:当判断条件变得复杂时(例如:包含连续两个4,或者包含4或7),字符串处理法往往比数学拆数法更直观,且不易出错。
- 局限性:涉及类型转换和额外的字符数组内存分配,运行效率略低于纯数学运算。
在实际编程与算法竞赛中,方法2(封装函数)通常是最佳实践,它兼顾了代码的整洁度与执行效率。而方法1 是理解底层数位拆解的基础,方法3则在处理复杂数位规则时提供了另一种灵活的解题视角。