对于最大回文积问题,递归中的堆栈级别太深(项目Euler)

时间:2019-07-17 00:33:18

标签: ruby recursion stack-overflow palindrome

我正在尝试为最大的回文产品problem实施递归解决方案

我想做的是将两个数字都从999开始,然后将num1向下迭代到100,然后在999处重新启动num1,然后将num2向下迭代1。

目标基本上是模仿嵌套的for循环。

def largest_palindrome_prod(num1 = 999, num2 = 999, largest_so_far = 0)
  prod = num1 * num2
  largest_so_far = prod if prod > largest_so_far && check_pal(prod)

  if num2 == 100
    return largest_so_far
  elsif num1 == 100
    largest_palindrome_prod(num1 = 999, num2 -= 1, largest_so_far)
  else
    largest_palindrome_prod(num1 -= 1, num2, largest_so_far)
  end
end

#I know this function works, just here for reference
def check_pal(num)
  num = num.to_s if num.is_a? Integer
  if num.length < 2
    true
  else
    num[0] == num[-1] ? check_pal(num[1..-2]) : false
  end
end

rb:10:in largest_palindrome_prod':堆栈级别太深`

我遇到了这个错误,该错误指的是maximum_palindrome_prod函数中的else语句,但是我不知道这是否会导致堆栈错误。

3 个答案:

答案 0 :(得分:4)

您没有无限递归错误。由于您输入的大小,堆栈即将耗尽空间。为了证明这一点,您可以使用2位数字范围而不是3位数字范围运行相同的功能。它返回的很好,表明您的逻辑没有缺陷。

如何解决这个问题?两种选择。

选项1:您根本不能在此处使用递归(而是使用常规的嵌套循环)

选项2:保留相同的代码并启用尾部调用优化:

# run_code.rb

RubyVM::InstructionSequence.compile_option = {
  tailcall_optimization: true,
  trace_instruction: false
}

require './palindrome_functions.rb'
puts largest_palindrome_prod
# => 906609 

请注意,由于我不完全了解的原因,必须在与正在运行的代码不同的文件中启用尾调用优化。因此,如果仅将compile_option行移动到palindrome_functions.rb文件,则它将行不通。

我无法真正为您提供尾部调用优化的完整说明(请在Wikipedia上查找),但据我所知,它是对递归函数的沉重优化,仅在递归调用位于端时有效。您的功能符合此条件。

答案 1 :(得分:2)

@maxpleaner回答了您的问题,并说明了如何使用递归来避免堆栈级错误。他还提到了简单循环(而不是采用递归)的选项(我希望他赞成)。以下是一种循环解决方案。搜索 1 中使用以下方法。

def check_ranges(range1, range2 = range1)
  range1.flat_map do |n|
    [n].product((range2.first..[n, range2.last].min).to_a)
  end.map { |x,y| x*y }.
      sort.
      reverse_each.
      find do |z|
        arr = z.digits
        arr == arr.reverse
      end              
end

让我们首先找到介于960和999之间的两个数字(如果有)的乘积中最大的回文数:

check_ranges(960..999)
  #=> nil 

没有。请注意,此计算非常便宜,只需要检查40*40/2 #=> 800个产品。接下来,找到最大回文,该回文等于920和999之间两个数字的乘积。

check_ranges(920..999)
  #=> 888888

成功!请注意,此方法会重新检查我们之前检查的800个产品。仅检查以下两个对brute_force的调用所代表的情况更有意义:

check_ranges(960..999, 920..959)
  #=> 888888 
check_ranges(920..959)
  #=> 861168 

第一个调用计算40*40 #=> 1600个产品;第二个800产品。

当然,我们尚未找到最大的回文产品。但是,我们对最大的产品确实有一个下限,我们可以利用它的优势。自

888888/999
  #=> 889

我们推断,如果两个数字的乘积大于888888,则两个数字都必须至少为889。因此,我们仅需检查:

check_ranges(889..999, 889..919)
  #=> 906609 
check_ranges(889..919)
  #=> 824428 

我们完成了。这告诉我们906609是两个3位数字(即回文)的最大乘积。

该问题并没有询问其乘积最大的回文中的两个数字是什么,但是我们可以轻松找到它们:

(889..999).to_a.product((889..919).to_a).find { |x,y| x*y == 906609 }
  #=> [993, 913] 
993*913
  #=> 906609

此外,让:

a = (889..999).to_a.product((889..919).to_a).map { |x,y| x*y }.
      sort.
      reverse

然后:

a.index { |n| n == 906609 }
  #=> 84

告诉我们,在发现回文(84)之前,只需要检查111*31 #=> 3441这类产品中最大的906609元素。

所有这些都需要组织成一个方法。尽管对于新手来说具有挑战性,但它应该是一个很好的学习经历。

1。测试更快的arr = z.digits; arr == arr.reverses = z.to_s; s == s.reverse会很有用。

答案 2 :(得分:0)

@maxpleaner已经回答,@ Cary Swoveland已经显示出使用rangesproduct的一种蛮力方式。我想使用嵌套循环显示另一种蛮力,更易于理解(IMO):

n = 9999

res = [0]
bottom = 10**(n.digits.size - 1)

n.downto(bottom) do |k|
  k.downto(bottom) do |j|
    # puts "#{k}, #{j}"
    res = [k, j, k * j] if check_pal(k * j) && k * j > res.last
  end
end

res
#=> [9999, 9901, 99000099]


我想可以进一步优化它,例如,使用

n.downto(n*99/100) do |k|
  k.downto(k*99/100) do |j|

在0.7秒内返回了[99979, 99681, 9966006699]


不需要,但这可以提高速度:

def check_pal(num)
  word = num.to_s
  word.reverse == word
end