想要像这样瞬间解决非常大的数字吗?使用更短的解决方案?
pp(908485319620418693071190220095272672648773093786320077783229)
=> [29, 41]
然后继续阅读:-)。这将是一个小小的旅程......
让我们首先在不改变逻辑的情况下让您的代码更好。只需将变量重命名为 m 和 k 并使用 each 循环:
def pp1(n)
ans = nil
(2...n).each do |m|
(2...n).each do |k|
if m**k == n
ans = [m, k]
break
end
end
end
ans
end
现在检查 n 到 200 大约需要 4.1 秒(与您原来的相同):
> require 'benchmark'
> puts Benchmark.measure { (1..200).each { |n| pp1(n) } }
4.219000 0.000000 4.219000 ( 4.210381)
第一次优化:如果我们找到解决方案,立即返回!
def pp2(n)
(2...n).each do |m|
(2...n).each do |k|
if m**k == n
return [m, k]
end
end
end
nil
end
遗憾的是,测试仍然需要 3.9 秒。下一个优化:如果mk已经太大了,我们不要再尝试更大的k了!
def pp3(n)
(2...n).each do |m|
(2...n).each do |k|
break if m**k > n
if m**k == n
return [m, k]
end
end
end
nil
end
现在速度如此之快,我可以在大约同一时间内运行 1000 次测试:
> puts Benchmark.measure { 1000.times { (1..200).each { |n| pp3(n) } } }
4.125000 0.000000 4.125000 ( 4.119359)
让我们改为 n = 5000:
> puts Benchmark.measure { (1..5000).each { |n| pp3(n) } }
2.547000 0.000000 2.547000 ( 2.568752)
现在不用计算m**k 这么多,让我们使用一个可以更便宜地更新的新变量:
def pp4(n)
(2...n).each do |m|
mpowk = m
(2...n).each do |k|
mpowk *= m
break if mpowk > n
if mpowk == n
return [m, k]
end
end
end
nil
end
遗憾的是,这几乎没有让它变得更快。但是还有另一个优化:当 mk 太大时,我们不仅可以忘记用更大的 k 尝试这个 m。如果它对于 k=2 来说太大了,即 m2 已经太大了,那么我们也不需要尝试更大的 m。我们可以停止整个搜索。让我们试试吧:
def pp5(n)
(2...n).each do |m|
mpowk = m
(2...n).each do |k|
mpowk *= m
if mpowk > n
return if k == 2
break
end
if mpowk == n
return [m, k]
end
end
end
nil
end
这现在可以在大约 0.07 秒内完成 5000 次测试!我们甚至可以在 6 秒内检查所有高达 100000 的数字:
> puts Benchmark.measure { (1..100000).each { |n| pp5(n) } }
5.891000 0.000000 5.891000 ( 5.927859)
无论如何,让我们看看大局。我们正在尝试m = 2..,对于每个m,我们尝试找到一个k,以便m**k == n。嘿,数学有一个解决方案!我们可以计算 k 为 k=logm(n)。让我们开始吧:
def pp6(n)
(2...n).each do |m|
k = Math.log(n, m).round
return if k < 2
return [m, k] if m**k == n
end
nil
end
再次测量:
> puts Benchmark.measure { (1..100000).each { |n| pp6(n) } }
28.797000 0.000000 28.797000 ( 28.797254)
嗯,慢点。好的,另一个想法:让外循环为k 而不是m。现在对于给定的 n 和 k,我们如何找到 m 使得 mk = n?取第k个根!
def pp7(n)
(2...n).each do |k|
m = (n**(1.0 / k)).round
return if m < 2
return [m, k] if m**k == n
end
nil
end
再次测量:
> puts Benchmark.measure { (1..100000).each { |n| pp7(n) } }
0.828000 0.000000 0.828000 ( 0.836402)
很好。 400000 怎么样,我在另一个答案中的分解解决方案在 2.9 秒内解决了?
> puts Benchmark.measure { (1..400000).each { |n| pp(n) } }
3.891000 0.000000 3.891000 ( 3.884441)
好的,这有点慢。但是...有了这个解决方案,我们可以解决真正大数字:
pp7(1000000035000000490000003430000012005000016807)
=> [1000000007, 5]
pp7(908485319620418693071190220095272672648773093786320077783229)
=> [29, 41]
> pp7(57248915047290578345908234051234692340579013460954153490523457)
=> nil
所有这些结果都会立即出现。分解解决方案也很快解决了 2941 的情况,但是对于 10000000075 的情况和最后的随机类型的情况,它很慢,因为分解很慢。
附:请注意,对数和平方根得到我们的浮点数。这可能会导致非常大的数字出现问题。例如:
irb(main):122:0> pp7(10**308 + 1)
=> nil
irb(main):123:0> pp7(10**309 + 1)
FloatDomainError: Infinity
from (irb):82:in `round'
from (irb):82:in `block in pp7'
from (irb):81:in `each'
from (irb):81:in `pp7'
from (irb):123
from C:/Ruby24/bin/irb.cmd:19:in `<main>'
在这种情况下,这是因为 10309 对于浮点数来说太大了:
> (10**309).to_f
=> Infinity
此外,如果数字足够大,可能会出现准确性问题。无论如何,您可以通过为对数和根编写整数版本来解决这些问题。但这是另一个问题。