在数学趣味题中,所谓的 “水仙花数”,指的是一个 100~999 之间的三位数,它的百位、十位、个位数字的立方之和恰好等于这个数本身。
比如 153 就是一个典型的水仙花数,因为 1³ + 5³ + 3³ = 1 + 125 + 27 = 153。在 C++ 中求解水仙花数,最关键的一步就是先把三位数的每一位数字单独拆出来,下面我们就从拆位方法开始讲起。
如何拆出三位数的每一位?
想要判断一个三位数是不是水仙花数,首先要解决一个问题:给定一个完整的三位数(比如 153),我们应该怎样把它的百位、十位和个位数字分别提取出来?
在 C++ 的算术运算符中,两个整数相除(/)会自动舍去小数部分,只保留整数商。而取余运算(%)则可以得到相除之后的余数。不妨拿 153 试算一下,配合这两个运算符就能把每一位数字拆出来:
153 % 10 = 3
153 / 100 = 1
153 / 10 % 10 = 5假设三位数保存在整型变量 n 中,上面这 3 个算式的拆位思路如下:
- 拆出个位:使用
n % 10计算。153 除以 10 的商是 15,余数是 3,任何正整数对 10 取余都能直接拿到最右边的个位数字。 - 拆出百位:使用
n / 100计算。将 153 除以 100 会直接舍去末尾两位,得到的整数商就是最左边的百位数字 1。 - 拆出十位:使用
n / 10 % 10计算。我们先算153 / 10去掉个位得到 15,再算15 % 10取出余数,就能拿到中间的十位数字 5。当然,写成先去掉百位再除以 10 的n % 100 / 10,算出来的结果也是一样的。
判断单个水仙花数
拆出百位、十位和个位上的数字之后,我们只需要算出这 3 个数字各自的立方和,再配合 if...else 语句判断结果是否等于原来的三位数 n 就可以了。请看下面例子。
示例 1:判断 153 是否为水仙花数
#include <iostream>
int main() {
int n = 153;
int a = n / 100; // 拆出百位
int b = n / 10 % 10; // 拆出十位
int c = n % 10; // 拆出个位
int sum = a * a * a + b * b * b + c * c * c;
if (sum == n) {
std::cout << n << " 是水仙花数\n";
} else {
std::cout << n << " 不是水仙花数\n";
}
return 0;
}运行结果如下。
153 是水仙花数分析:
上面这个例子非常简单,变量 a、b、c 依次保存从 n 中拆出来的百位 1、十位 5 和个位 3。小伙伴们要注意,n / 100 和 n % 10 只是读取 n 的值参与运算,并没有修改 n 本身,因此拆位结束后 n 的值依然是 153。
随后程序算出立方和 sum 为 153,满足条件 sum == n,于是输出 “153 是水仙花数”。如果我们把开头的 n 改成 123,算出来的 sum 就是 36(不等于 123),程序就会进入 else 分支输出 “123 不是水仙花数”。
下面代码中,变量 b 的最终值是 7,小伙伴们可以思考一下为什么。
int n = 370;
int b = n / 10 % 10;注意 计算整数立方时,直接写成 a * a * a 比调用 <cmath> 中的 std::pow() 更稳妥。因为 std::pow() 按浮点数计算,在某些环境下可能产生类似 124.999999 的微小误差,赋给 int 时会被截断成 124 而导致判断出错。
找出所有的水仙花数
既然水仙花数都是三位数,我们只需要在上面判断逻辑的外面套上一层 for 循环,让变量 n 从 100 依次遍历到 999,就能把所有的水仙花数全部找出来。
示例 2:遍历 100~999 寻找水仙花数
#include <iostream>
int main() {
std::cout << "100~999 之间的水仙花数有:";
for (int n = 100; n <= 999; n++) {
int a = n / 100; // 拆出百位
int b = n / 10 % 10; // 拆出十位
int c = n % 10; // 拆出个位
int sum = a * a * a + b * b * b + c * c * c;
if (sum == n) {
std::cout << n << " ";
}
}
std::cout << '\n';
return 0;
}运行结果如下。
100~999 之间的水仙花数有:153 370 371 407分析:
从运行结果可以看到,在 100~999 之间一共有 153、370、371、407 这 4 个水仙花数。代码中的 for 循环让 n 从 100 递增到 999,每一轮都拆出当前三位数的各位数字并算出立方和 sum,只要满足 sum == n 就将它打印出来。
阿姆斯特朗数与多位数拆位
水仙花数在数学上其实是 “阿姆斯特朗数” 在三位数下的一种特例。更宽泛地说,如果一个 k 位正整数,它各位数字的 k 次方之和恰好等于它本身,就称为阿姆斯特朗数。例如四位数中的 “四叶玫瑰数” 1634,就满足 1⁴ + 6⁴ + 3⁴ + 4⁴ = 1634。
如果待处理的正整数位数不固定,我们可以先把它存进临时变量 temp 中,再配合 while (temp > 0) 循环从低位到高位逐位拆开。每一轮先通过 temp % 10 取出当前的个位,再执行 temp /= 10 去掉最后一位,重复到 temp 变成 0 即可。
