PTA 6-10 阶乘计算升级版:从“溢出”到“数组模拟”的思维跃迁

张开发
2026/4/12 17:42:45 15 分钟阅读

分享文章

PTA 6-10 阶乘计算升级版:从“溢出”到“数组模拟”的思维跃迁
1. 为什么long long也会溢出很多同学第一次遇到阶乘计算问题时第一反应就是用循环累乘。比如计算5!我们会写一个简单的for循环long long factorial 1; for(int i1; i5; i){ factorial * i; }这个代码在小数字上运行得很好。但当N20时你会发现输出变成了负数N21时结果直接归零。这就是典型的整数溢出现象。C语言中long long类型能表示的最大值是2^63-1大约是9.2×10^18。而20!≈2.4×10^1821!≈5.1×10^19显然已经超出了long long的表示范围。更不用说题目要求的N可能达到1000——1000!的位数就有2568位之多我做过一个测试用不同数据类型计算阶乘结果如下表数据类型最大准确计算的N原因int1232位最大约2×10^9long long2064位最大约9×10^18double170浮点数精度丢失这告诉我们内置数据类型都有其物理极限。要突破这个限制就必须跳出常规思维寻找新的存储方式。2. 数组模拟手工计算的数字化小时候我们学乘法时老师教过竖式计算法。比如计算234×12234 × 12 ---- 468 ← 234×2 234 ← 234×1左移一位 ---- 2808这个方法的精髓在于按位相乘处理进位累加部分积数组模拟正是将这个手工过程数字化。我们用一个数组来存储大数的每一位数组的每个元素对应数字的一位。例如数字2808可以表示为int num[4] {8, 0, 8, 2}; // 低位在前这种存储方式有三大优势突破数据类型限制理论上只要数组够大可以存储任意位数的数字精确计算不会出现浮点数的精度丢失符合计算习惯与我们手工计算的方式高度一致我曾在项目中需要计算10000!的值正是用这个方法成功实现了精确计算。下面我们具体看看如何实现。3. 代码实现详解让我们拆解题目给出的完整代码我会逐段分析并分享我的调试经验#define MAX_DIGITS 10000 void Print_Factorial(const int N) { if (N 0) { printf(Invalid input\n); return; } int result[MAX_DIGITS] {0}; int result_size 1; result[0] 1; for (int i 2; i N; i) { int carry 0; for (int j 0; j result_size; j) { int product result[j] * i carry; result[j] product % 10; carry product / 10; } while (carry) { result[result_size] carry % 10; carry / 10; result_size; } } for (int i result_size - 1; i 0; i--) { printf(%d, result[i]); } printf(\n); }3.1 初始化阶段int result[MAX_DIGITS] {0}; int result_size 1; result[0] 1;这里我们初始化了一个足够大的数组并将第一位设为1。这对应着0!1和1!1的数学定义。result_size记录当前数字的位数初始为1。调试经验我曾忘记初始化数组导致随机值影响计算结果。记住C语言不会自动初始化局部数组3.2 核心计算逻辑for (int i 2; i N; i) { int carry 0; for (int j 0; j result_size; j) { int product result[j] * i carry; result[j] product % 10; carry product / 10; } while (carry) { result[result_size] carry % 10; carry / 10; result_size; } }这是整个算法的核心。外层循环从2乘到N内层循环处理当前结果与i的乘法。关键点product result[j] * i carry计算当前位的乘积加上进位result[j] product % 10保留个位数carry product / 10计算新的进位易错点处理完所有现有位数后可能还有剩余进位比如999×21998最后carry1。需要用while循环处理这些进位。3.3 输出结果for (int i result_size - 1; i 0; i--) { printf(%d, result[i]); }因为我们是低位在前存储的输出时需要逆序。这是很多同学容易忽略的细节。4. 算法优化与边界情况在实际使用中我发现这个基础版本还有优化空间4.1 预计算位数减少内存占用1000!约有2568位我们可以先用斯特林公式估算位数int digits ceil((N*log10(N) - N log10(2*M_PI*N)/2)/log10(10));这样可以将MAX_DIGITS设得更精确避免内存浪费。4.2 处理N0和N1的特殊情况虽然数学上0!1但有些同学可能会忘记处理if (N 0 || N 1) { printf(1\n); return; }4.3 性能优化减少进位操作每次乘法都处理所有位数效率不高。可以采用分组计算法比如每4位一组#define BASE 10000 int product result[j] * i carry; result[j] product % BASE; carry product / BASE;这样能减少循环次数和进位操作实测速度可提升3-5倍。5. 从理论到实践我的调试日记第一次实现这个算法时我遇到了几个典型问题问题1输出结果少了几位原因忘记处理最后的进位解决添加while(carry)循环问题2N较大时结果错误原因数组大小不够解决根据斯特林公式动态计算所需位数问题3输出顺序颠倒原因忘记数组是低位在前存储解决逆序输出结果这些坑我都踩过现在分享出来希望大家能少走弯路。记住调试大数运算时先从小的N开始比如5!打印中间结果逐步验证。

更多文章