目录
关于自幂数的概念:
如果在一个固定的进制中,一个n位自然数等于自身各个数位上数字的n次幂之和,则称此数为自幂数。
例如:在十进制中,153是一个三位数,各个数位的3次幂之和为1^3 + 5^3 + 3^3=153,所以153是十进制中的自幂数;1634 = 1^4 + 6^4 + 3^4 + 4^4,则1634也是十进制中的一个自幂数,如此类推。
各种自幂数的名称:
一位自幂数:独身数(1,2,3,4,5,6,7,8,9)
两位自幂数:没有
三位自幂数:水仙花数(153,370,371,407)
四位自幂数:四叶玫瑰数(1634,8208,9474)
五位自幂数:五角星数(54748,92727,93084)
六位自幂数:六合数(548834)
七位自幂数:北斗七星数(1741725,4210818,9800817,9926315)
八位自幂数:八仙数(24678050,24678051,88593477)
九位自幂数:九九重阳数(146511208,472335975,534494836,912985153)
十位自幂数:十全十美数(4679307774)
实现思路:
1、根据当前数的位数 n 来构造一个数组,存放的是 0 - 9 各数的 n 次方。如当前数是3位的,则数组为 powArr[10] = [0, 1, 8, 27, 64, 125, 216, 343, 512, 729]。
每次位数改变时,及时更新 2 - 9 位上的数。
2、首先需要求出当前数 各数位 上的数的 n 次方 之和(当前数最高位是几位,n 就是几)。可以用循环对 10 取余的方法来实现。
设当前数为 num,各数位上的数的 n 次方 和为 sum,n 为当前数的位数。
3、当 sum < num 时,即和小于当前数,用当前数 num 减去 num 的最低位数值 minSite,即使之最低位为 0, 得到 tempNum,再减去 (sum - minSite ^ n), 得到两者的差值 tempGap。tempGap 再开 n 次方,得到结果 result。
result 表示: sum 如果再得到 result 的 n 次方,就能与 tempNum 相同。
此时对 result 向上取整得到 minBalance,若 minBalance == minSite,则再将 minBalance 自加 1。
根据得到的 minSite,若minBalance < 10 ,则使 num = tempNum + minBalance,sum = sum - minSite ^ n + minBalance ^ n,则当前可以确定:
num - 1 > sum - (num % 10) ^ n + (num % 10 - 1) ^ n;
num <= sum;
然后再以当前的 num 进入下一次循环再做判断。
若 minBalance > 9 ,则 num 直接十位进一,个位置零,再进入下一次循环。
以 401 、437进入循环开始举例:
①401:
当前值 num = 401 ,大于 sum = 4 ^ 3 + 0 ^ 3 + 1 ^ 3 = 65;
tempNum = 400 - 1 = 400,sum = 4 ^ 3 = 64;
对 (tempNum - sum)开 3 次方,得 result = 6.9382....;
可知,sum 再加 result ^ 3 就能与tempNum 相等。
对 result 做向上取整。向上取整的目的是为了让 tempNum + minBalance 大于 num 且 temp + minBalance <= 与之对应的 最小的 sum。取整后,minBalance = 7。此时 minBalance < 10 且 minBalance > minSite,满足要求,则 sum = tempNum + minBalance = 407,再进入下一个循环。
可以验证判断:
tempNum + floor(result) = 406 > 4 ^ 3 + 6 ^ 3 = 280; 则 402 —— 405 都大于各自对应的 sum。
tempNum + ceil(result) = 407 = 4 ^ 3 + 7 ^ 3 = 407;
此时407进入下一个循环,将被命中。
②437:
当前值 num = 437 ,大于 sum = 4 ^ 3 + 3 ^ 3 + 7 ^ 3 = 434;
tempNum = 437 - 7 = 430,sum = 4 ^ 3 + 3 ^ 3 = 91;
对 (tempNum - sum)开 3 次方,得 result = 6.9726;
可知,sum 再加 result ^ 3 就能与tempNum 相等。
对 result 再做向上取整,但向上取整后 minBalance = 7,与 minSite 相等,需要再自加一,所以最后得到 minBalance = 8,8 < 10,则num 变为 408,进行下一次循环。
4、当 sum > num 时,可以断定,最低位不管是多少(即最低位在不引起进位的情况下不管再加多少),都不可能大于其对应的 sum。
此时可直接将最低位置零,十位加1,再继续循环。
Python 实现:
import math
def method(startDigit, digit): # 传入开始位数,最高的位数 (100,5)即 100 ——99999
num = 10 ** (startDigit - 1)
curLength = len(str(num))
powArr = [] # 暂存 (0-9)的n次幂 数组
for i in range(0, 10):
powArr.append(i ** curLength)
while(curLength <= digit): # 当位数不超过指定的最高位数
tempNum = num # 用来取余
sum = 0 # 记录每个数的每位的n次幂之和
while(tempNum > 0): # 初始化 各位数之和 sum
sum += powArr[tempNum % 10]
tempNum = tempNum // 10
minSite = num % 10 # 最低位
if(sum == num): # 相等时
print('!! num: ', num)
if(curLength != 1 and minSite != 0):
num = num + 10 - minSite
else:
num += 1
elif(sum < num): # 和小于该数
temp = math.ceil(pow((num - minSite) - (sum - powArr[minSite]), 1 / curLength))
if(temp == minSite):
temp += 1
num = num - minSite + (temp if temp <= 10 else 10)
else: # 和大于该数
num += 10 - minSite
if(len(str(num)) > curLength): # 是否需要重置数组
curLength += 1
for i in range(2, 10):
powArr[i] = i ** curLength
C 实现:
使用同样的算法,移植到 C 中。
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<string.h>
#include<time.h>
clock_t start, stop;
double duration; //记录被测函数运行时间,以秒为单位
void action(int startSite, int digit){ // 传最低位和最高位,如(3,4) 即 100-9999
start = clock(); // 开始计时
long count = 0; // 记录循环的此次数
int num = pow(10, startSite - 1); // 设置初始数值
int powArr[10] = {0}; // 存储(0-9)分别的n次方
for(int i = 1 ; i < 10 ; i ++){
powArr[i] = pow(i, startSite);
}
char p[20]; // 将 num 转成字符串,存在 p 中,方便计算位数
itoa(num, p, 10);
int currentLen = (int)strlen(p);
while(currentLen <= digit){
int tempNum = num;
int sum = 0;
while(tempNum > 0){
sum += powArr[tempNum % 10];
tempNum /= 10;
}
int minSite = num % 10; // 最低位
if(sum == num){
printf("%d\n", num);
if(currentLen != 1 && minSite != 0){
num += 10 - minSite;
}else{
num ++;
}
}else if(sum < num){
int temp = ceil(pow((num - minSite) - (sum - powArr[minSite]), 1.0 / currentLen));
if(temp == minSite){
temp ++;
}
num = num - minSite + (temp <= 10 ? temp : 10);
}
else{
num += 10 - minSite;
}
itoa(num, p, 10);
if(currentLen < (int)strlen(p)){
currentLen ++;
for(int i = 1 ; i < 10 ; i ++){ // 重置数组
powArr[i] = pow(i, currentLen);
}
}
count ++;
}
stop = clock();
duration = (double)(stop - start) / CLOCKS_PER_SEC;
printf("\nspend time: %lf s\n",duration);
printf("count : %ld ",count);
}
int main(){
action(1, 8);
}
使用C运行,查找 1 - 99,999,999(一位到八位)范围内的自幂数,运行时长花费 1.3 s左右,循环的次数为:14,465,229 次,约为该范围内所有数字总数的 七分之一。