使用BigInt求解Sam Loyd黑斯廷斯谜题的性能优化问题
优化BigInt完全平方检查及佩尔方程最优解法
一、针对暴力遍历的BigInt代码优化
如果需要保留暴力遍历思路,可从以下方向降低BigInt计算开销:
1. 递推计算目标值,减少BigInt乘法
每次计算 (13n^2 +1) 时,无需重复计算 (n^2),利用前一次结果递推:
(13(n+1)^2 +1 = 13n^2 +1 +13*(2n+1))
将BigInt乘法次数从O(n)压缩到近似O(1)每次:
const limit = 7_000_000; var a = []; console.time('big int optimized'); let current = 14n; // 13*1²+1=14 if (perfectSquare(current)) a.push(1n); for (let n = 2n; n < limit; n++) { current += 13n * (2n - 1n); if (perfectSquare(current)) { a.push(n); } } console.log(a.join(', ')); console.timeEnd('big int optimized');
2. 优化完全平方数检查逻辑
(1)增加多模预过滤
在进入开方计算前,用更多模值快速排除非平方数,减少开方调用次数:
function perfectSquare(n) { // 末位过滤:平方数末位只能是0/1/4/5/6/9 const lastDigit = n % 10n; if (![0n,1n,4n,5n,6n,9n].includes(lastDigit)) return false; // 模9过滤:平方数模9只能是0/1/4/7 const mod9 = n % 9n; if (![0n,1n,4n,7n].includes(mod9)) return false; // 原有模4/模8过滤 while ((n & 3n) === 0n && n !== 0n) { n >>= 2n; } if ((n & 7n) !== 1n) return false; const sqrtVal = integerSQRT(n); return n === sqrtVal * sqrtVal; }
(2)优化整数开方的初始值
避免BigInt转字符串的耗时操作,改用二进制位长度计算初始近似值:
var integerSQRT = function(value) { if (value < 2n) return value; if (value < 16n) return BigInt(Math.sqrt(Number(value)) | 0); let x1; if (value < 4503599627370496n) { x1 = BigInt(Math.sqrt(Number(value))|0) - 3n; } else { // 用二进制位长度生成初始值,比转字符串快数倍 const bitLength = value.toString(2).length; x1 = 1n << ((BigInt(bitLength) - 1n) >> 1n); } let x0; do { x0 = x1; x1 = ((value / x0) + x0) >> 1n; } while (x0 !== x1 && x0 !== x1 - 1n); return x0; }
(3)循环变量用Number(上限在安全整数范围时)
若limit未超出Number.MAX_SAFE_INTEGER,循环变量用Number类型,计算时再转BigInt,比BigInt循环更快:
for (let n = 2; n < limit; n++) { const nBig = BigInt(n); current += 13n * (2nBig - 1n); if (perfectSquare(current)) { a.push(nBig); } }
二、最优解法:利用佩尔方程递推生成解
你的问题本质是求解佩尔方程 (y^2 - 13n^2 = 1),这类方程无需暴力遍历,所有解可通过最小解递推生成,性能提升几个数量级:
- 方程的最小解为:(y=649, n=180)(验证:(649^2 -13×180^2=421201-421200=1))
- 后续解通过以下公式递推:
[
\begin{cases}
y_{k+1} = 649×y_k + 13×180×n_k \
n_{k+1} = 649×n_k + 180×y_k
\end{cases}
]
实现代码
function generatePellSolutions(count) { const solutions = []; let y = 649n; let n = 180n; solutions.push(n); for (let i = 1; i < count; i++) { const nextY = 649n * y + 13n * 180n * n; const nextN = 649n * n + 180n * y; solutions.push(nextN); y = nextY; n = nextN; } return solutions; } // 生成前13个解 console.time('pell equation'); const solutions = generatePellSolutions(13); console.log(solutions.join(', ')); console.timeEnd('pell equation');
这段代码生成前13个解仅需数毫秒,无论n多大都能瞬间生成,完全无需遍历。
内容的提问来源于stack exchange,提问作者Edge
相关产品推荐
相关产品推荐

