水仙花數是「位運算與數學性質結合」的經典算法題。PAT「循環-11」要求找出所有 N 位水仙花數(N≥3)。本文從定義建模、暴力枚舉、預計算優化、數學剪枝、多語言實現五個維度,提供完整解題體系。
N 位十進制正整數 x,若滿足:
x = d_{N-1} d_{N-2} ... d_1 d_0
x = Σ_{i=0}^{N-1} (d_i)^N
則 x 為 N 位水仙花數。關鍵約束: N≥3,無前導零(即最高位 d_{N-1} ∈ [1,9])。
| N | 水仙花數列表 | 數量 |
|---|---|---|
| 3 | 153, 370, 371, 407 | 4 |
| 4 | 1634, 8208, 9474 | 3 |
| 5 | 54748, 92727, 93084 | 3 |
| 6 | 548834 | 1 |
| 7 | 1741725, 4210818, 9800817, 9926315 | 4 |
| 8 | 24678050, 24678051, 88593477 | 3 |
規律: 隨 N 增加,水仙花數極其稀疏,N=39 後不存在(證明基於 9^N * N < 10^{N-1})。
#include <stdio.h>
#include <math.h>
int N;
int is_narcissistic(int x) {
int original = x;
int sum = 0;
while (x > 0) {
int digit = x % 10;
sum += (int)pow(digit, N); // pow 返回 double,需轉換
x /= 10;
}
return sum == original;
}
int main() {
scanf("%d", &N);
int start = (int)pow(10, N-1);
int end = (int)pow(10, N);
for (int i = start; i < end; i++) {
if (is_narcissistic(i)) {
printf("%d\n", i);
}
}
return 0;
}
#include <stdio.h>
#include <math.h>
int N;
int pow_table[10]; // 0^N ~ 9^N
int is_narcissistic_fast(int x) {
int original = x;
int sum = 0;
while (x > 0) {
sum += pow_table[x % 10];
x /= 10;
}
return sum == original;
}
int main() {
scanf("%d", &N);
// 預計算 0~9 的 N 次冪
for (int d = 0; d <= 9; d++) {
pow_table[d] = (int)pow(d, N);
}
int start = 1;
for (int i = 1; i < N; i++) start *= 10; // 10^(N-1) 整數運算
int end = start * 10; // 10^N
for (int i = start; i < end; i++) {
if (is_narcissistic_fast(i)) {
printf("%d\n", i);
}
}
return 0;
}
性能提升: 將 pow() 從內層循環(每數字調用 N 次)提升到預計算階段(僅 10 次),N=7 時速度提升 ~100 倍。
利用 數字多重集合 性質:水仙花數只取決於數字組成,不取決於排列順序。
SS 的數字組成是否與原組合一致S 的所有排列(去重)搜索空間從 9×10^{N-1} 降為 C(N+9, 9),N=7 時從 900萬降至 11440,指數級剪枝。
#include <stdio.h>
int main() {
for (int i = 1; i <= 9; i++) { // 百位 1-9
for (int j = 0; j <= 9; j++) { // 十位 0-9
for (int k = 0; k <= 9; k++) { // 個位 0-9
int num = 100*i + 10*j + k;
int sum = i*i*i + j*j*j + k*k*k;
if (num == sum) {
printf("%d\n", num);
}
}
}
}
return 0;
}
import java.util.Scanner;
public class Narcissistic {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int[] pow = new int[10];
for (int d = 0; d <= 9; d++) {
pow[d] = (int)Math.pow(d, N);
}
int start = 1;
for (int i = 1; i < N; i++) start *= 10;
int end = start * 10;
for (int i = start; i < end; i++) {
int sum = 0, x = i;
while (x > 0) {
sum += pow[x % 10];
x /= 10;
}
if (sum == i) System.out.println(i);
}
}
}
| 錯誤 | 後果 | 修正 |
|---|---|---|
pow(digit, N) 未轉 int | double 比較精度問題 | (int)pow(digit, N) 或預計算表 |
範圍 start = pow(10, N-1) 精度誤差 | 如 1000 變 999 | 用整數循環乘法計算 10^(N-1) |
| N=1,2 時未處理 | 輸出 0-9 或無輸出 | 題目限定 N≥3,可加斷言 assert(N>=3) |
未包含 math.h | 隱式聲明警告/錯誤 | #include <math.h> 並鏈接 -lm |
| 算法 | 時間複雜度 | 空間複雜度 | 適用 N | 實現難度 |
|---|---|---|---|---|
| 暴力枚舉 + pow | O(N × 10^N) | O(1) | ≤5 | 低 |
| 預計算冪值表 | O(N × 10^N) | O(1) | ≤7 | 低 |
| 三重循環 | O(1) 常數 | O(1) | 只 N=3 | 極低 |
| 組合生成剪枝 | O(C(N+9,9) × N) | O(N) | ≤39 | 高 |
水仙花數問題展示了「預計算空間換時間」與「數學性質剪枝」兩大算法優化核心思想。對於 PAT 考試,預計算冪值表版本已足夠應對 N≤7 的測試數據;若挑戰更大 N,組合生成剪枝是理論最優路徑。建議動手實現預計算版並測量 N=7 耗時,體會數量級優化的快感。