【问题标题】:How to turn this memoized recursive solution into a 'bottom-up' iterative one?如何将这种记忆递归解决方案变成“自下而上”的迭代解决方案?
【发布时间】:2022-01-05 22:06:27
【问题描述】:

我有一个使用动态编程的问题的解决方案。我需要帮助将其从递归解决方案转变为迭代解决方案。

该函数接受一个数字并遵循三个规则:

  1. 它可以将数字分成两半
  2. 可以减一
  3. 可以加一个

直到数字为 1。我的目标是找到执行此操作所需的最少步骤数。

这是我的解决方案:

def solution(n):
    n = int(n)
    memo = {}
    return memoized_fuel_injection_perfection(n, memo)
        
def memoized_fuel_injection_perfection(n, memo):
    if n == 1:
        return 0
    if n == 2:
        return 1
    if n in memo:
        return memo[n]
    if n % 2 == 0:
        if n not in memo:
            memo[n] = memoized_fuel_injection_perfection(n//2, memo) + 1
        return memo[n]
    return min(memoized_fuel_injection_perfection(n-1, memo), memoized_fuel_injection_perfection(n+1, memo)) + 1

但是当我输入长度超过 300 位的数字时,我遇到了递归错误。我怎样才能把它变成一个迭代的解决方案?感谢您提供任何帮助或指导。

这是我创建的一个迭代解决方案,但我收到了带有非常大输入的 MemoryError。有什么方法可以优化存储变量,这样我就不必为每个数字计算它们?

def solution(n):
    memo = {}
    memo[0] = 0
    memo[1] = 0
    memo[2] = 1
    n = int(n)
    for i in range(3, n+1):
        if i % 2 == 0:
            memo[i] = memo[i//2] + 1
        else:
            memo[i] = min(memo[i//2], memo[i//2 + 1]) + 2
    
    return memo[n]

【问题讨论】:

  • 是 RecursionError 唯一的问题吗?即,如果我们可以让递归解决方案适用于大量输入,您对此满意吗?
  • 是的,那行得通。虽然这是一个作业,但我不允许导入像 'sys' 这样的库来增加递归堆栈限制。
  • “大于 300 位的数字”您能否提供一个示例输入和预期输出?
  • 使用sys 增加递归限制是愚蠢的。我们可以使用continuation-passing style 将任何递归程序转换为迭代程序。
  • 打印(溶液( '10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003333000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003483983333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333'))应该返回2467 跨度>

标签: python recursion iteration dynamic-programming


【解决方案1】:

您说您在编写迭代解决方案时遇到的问题是使用memoized_fuel_injection_perfection(n+1, memo),这使得确定计算结果的顺序变得很棘手。关键是您不能无限地重复此代码路径.如果可以,即使您的递归解决方案也将无效。

在 +1 或 -1 操作之后,您总是执行除以 2。我们可以将 +1 或 -1 与除以 2 融合,产生一个不能增加数字的操作。迭代解决方案的核心将如下所示:

if n % 2 == 0:
    table[n] = table[n//2] + 1
else:
    table[n] = min(table[n//2], table[n//2+1]) + 2

你能从那里完成事情吗? (您需要一种方法来避免计算每个小于n的正整数的结果。)

【讨论】:

  • 感谢您的回复,为什么'else'语句中是+2?不会是 +1,因为它是最小值中的最小值,加上 +1?
  • @s_jack_frost:它是 +2,因为我们合并了两个步骤,一个 +/-1 步骤和一个除以 2 步骤。
  • 有道理 - 我有我的实现,我在非常大的输入上得到了 MemoryError。我认为我没有优化正确存储结果,并为每个数字计算结果。我将用我的新解决方案更新我原来的帖子
【解决方案2】:

这是我的尝试:

def solution(n):
    def is_even(n): # helper function
        return n % 2 == 0

    possible_nodes = {1} # 1 is the destination
    consider = [n] # stack: numbers to be considered
    while consider: # as long as non-empty
        x = consider.pop() # now think about where can we move from x
        if x in possible_nodes: # if it is already handled before
            continue
        if is_even(x): # if even, we just halve it
            consider.append(x//2)
        else: # otherwise, -1 or +1
            consider += [x-1, x+1]
        possible_nodes.add(x) # mark x as 'considered'

    steps = {1: 0} # dict to store min steps

    for x in filter(is_even, sorted(possible_nodes)): # odds calculated only when needed
        if x//2 not in steps: # if x//2 was not computed, x//2 must be odd
            steps[x//2] = min(steps[x//2 - 1], steps[x//2 + 1]) + 1
        steps[x] = steps[x//2] + 1

    return steps[n] if is_even(n) or n == 1 else min(steps[n-1], steps[n+1]) + 1

n = 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003333000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003483983333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
print(solution(n)) # 2467

print(*(solution(i) for i in range(1, 21)))
# 0 1 2 2 3 3 4 3 4 4 5 4 5 5 5 4 5 5 6 5

代码基本上由两个步骤组成。第一步,枚举n中所有可能的步骤,规则为x,

  1. 如果x是偶数,我们只考虑x//2作为下一步;您的代码中也采用了这种策略;
  2. 如果x 是奇数,我们将x-1 和x+1 视为下一步。

我这样做是因为计算直到 n 的所有值的最小步数是浪费的。 (实际上一开始我尝试通过启动[None] * n 之类的方法来做到这一点,但似乎python 无法处理这么长的列表。即使可以,我想这也会非常慢。)

下一步是计算最小步数,从最小的数字开始。我通过“不急切地为奇数x 计算steps[x]”来解决访问steps[x+1] 的问题。我们只热切地计算偶数x 的steps[x],而懒惰地计算奇数的x。

理由如下;到需要奇数x 时,对于某些偶数 k,x 必须是k//2 - 1 或k//2 + 1,这必须大于x + 1 .因为x + 1 是偶数,所以step[x+1] 肯定已经通过for 循环的构造计算出来了。

【讨论】:

  • 感谢您的回复。在我的 ide 上运行它需要一些时间
猜你喜欢
  • 2013-07-22
  • 1970-01-01
  • 1970-01-01
  • 2020-06-07
  • 2012-07-26
  • 1970-01-01
  • 2019-11-25
  • 2020-08-09
  • 1970-01-01
相关资源
最近更新 更多