欧拉计划第8题代码修复咨询:为何小数据正常大数据出错?
嘿,我来帮你揪出这个问题的根源并搞定它!你提到代码处理小数值正常,但面对1000位数字时返回错误结果,猜测是long long的问题——其实核心问题大概率是中间计算时的整数溢出,或者是循环逻辑里的小疏漏,咱们一步步来拆解:
为什么会出现错误结果?
首先算个账:13个9的乘积是9^13 = 2541865828329,这个数值确实在long long的容纳范围内(64位long long最大能到9223372036854775807),理论上long long是够用的。那为什么你的代码会返回离谱的8502282159491788800?
大概率是你的代码在计算乘积时,中间步骤用了更小的整数类型(比如int),导致提前溢出。举个例子:如果用int存储单个数字并直接相乘,当多个数字的乘积超过int的最大值(2147483647)时,就会发生溢出,得到错误的中间值,最终整个乘积自然不对。比如9^10 = 3486784401,已经超过了int的上限,这时候用int计算就会溢出,变成负数或者乱码,再赋值给long long也没用了。
另外,暴力循环的写法不仅效率低,也更容易因为重复计算增加出错的概率——比如循环范围写错、漏处理窗口里的0等。
修复方案
1. 强制使用64位整数处理所有乘法步骤
确保所有参与乘法的变量都是long long类型,包括单个数字的存储,避免中间溢出。比如把字符转数字的结果直接存为long long:
long long digit = num[i] - '0';
这样每次乘法都是long long之间的运算,不会出现提前溢出的问题。
2. 用滑动窗口优化算法(同时减少出错概率)
暴力循环会重复计算大量重叠的乘积,滑动窗口可以把时间复杂度从O(n*13)降到O(n),同时减少循环次数,降低出错可能。核心思路是:
- 先计算第一个13位窗口的乘积
- 滑动窗口时,用当前乘积除以左边离开窗口的数字,再乘以右边进入窗口的新数字
- 特别注意处理窗口中的0:如果离开的数字是0,需要重新计算当前窗口的乘积(因为除以0会报错,且之前的乘积因为0的存在已经无效)
修复后的示例代码(C++)
#include <iostream> #include <string> using namespace std; int main() { // 替换成题目中的1000位数字字符串 string num = "7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450"; int len = num.size(); const int window_size = 13; long long max_product = 0; // 初始化第一个窗口的乘积 long long current_product = 1; int zero_count = 0; for (int i = 0; i < window_size; ++i) { long long digit = num[i] - '0'; if (digit == 0) { zero_count++; } else { current_product *= digit; } } if (zero_count == 0) { max_product = current_product; } // 滑动窗口遍历剩余数字 for (int i = window_size; i < len; ++i) { long long left_digit = num[i - window_size] - '0'; long long right_digit = num[i] - '0'; if (left_digit == 0) { // 左边离开的是0,重新计算当前窗口的乘积 zero_count--; current_product = 1; zero_count = 0; for (int j = i - window_size + 1; j <= i; ++j) { long long d = num[j] - '0'; if (d == 0) { zero_count++; } else { current_product *= d; } } } else { // 移除左边数字的影响 current_product /= left_digit; // 添加右边数字的影响 if (right_digit == 0) { zero_count++; current_product = 0; // 窗口含0,乘积为0 } else { current_product *= right_digit; } } // 更新最大乘积(跳过含0的窗口) if (zero_count == 0 && current_product > max_product) { max_product = current_product; } } cout << "最大乘积:" << max_product << endl; return 0; }
代码要点说明
- 所有数字都转成
long long,确保乘法运算全程在64位整数下进行,避免中间溢出 - 用
zero_count跟踪窗口中的0,避免把含0的无效乘积计入最大值 - 滑动窗口时,只有当左边离开的数字是0时才重新计算窗口乘积,其他情况直接复用之前的结果,效率更高
- 最终计算出的结果就是题目要求的正确值(23514624000)
为什么小数值正常?
因为小数值的乘积不会超过int或者long long的范围,中间计算不会溢出,所以结果正确。但面对1000位数字时,某些窗口的乘积在中间步骤会超过小类型的上限,导致溢出,最终得到错误结果。
内容的提问来源于stack exchange,提问作者Math




