【问题标题】:Why do we pop from the list at the end of each backtrack iteration?为什么我们在每次回溯迭代结束时从列表中弹出?
【发布时间】:2021-05-02 11:21:07
【问题描述】:

我有一个解决以下问题的方法:https://leetcode.com/problems/combinations/

List[List[int]]:
        def backtrack(first = 1, curr = []):
            # if the combination is done
            if len(curr) == k:  
                output.append(curr[:])
            for i in range(first, n + 1):
                # add i into the current combination
                curr.append(i)
                # use next integers to complete the combination
                backtrack(i + 1, curr)
                # backtrack
                curr.pop()
        
        output = []
        backtrack()
        return output

我的问题是关于curr.pop() 为什么我们每次迭代都弹出curr 组合?不应该有一些条件,比如curr已经在输出中了吗?

另一个问题是递归调用backtrack(i+1, curr) - 当它被调用时,我是否正确地说'i+1' 代替了主函数中的'first'?

【问题讨论】:

  • 在这段代码中,append扮演push的角色。并且函数调用的工作完全相同无论它们是否是递归的。
  • curr.pop() 正在删除列表中的最后一项,即i,因为它是使用curr.append(i) 添加的。

标签: python recursion recursive-backtracking


【解决方案1】:

curr 组合的(副本)在递归的最深级别输出(嗯,它应该是最深的,目前代码甚至继续循环当预先知道它是徒劳的,即没有机会产生任何输出,即当curr 的长度大于k)。

组合是建立在路上的,一次一个元素。

元素被添加,递归被输入(最终到达最深层次,组合的副本被收集到输出中)然后递归展开,最终退出递归调用 --- 所以我们必须删除我们放入的元素,以便我们可以将 next 元素放入curr,在该for ... range ... 循环的下一次迭代(其中应该被if len(curr) < k: ....看守。

所以是的,i+1 first"new" 值 -- 在 next for ... range ... 循环中, 更深一层。因此这里发生的情况是,递归实际上构建了嵌套循环结构,其中最深的循环具有完全设置和填充的curr,因此将其添加到output

或者在伪代码中:

   for i1 in range( 1, n+1):
      for i2 in range( i1+1, n+1):
         .........
            for ik in range( ik1+1, n+1):
               output.append( [i1,i2,...,ik1,ik] )

(除了代码效率低下,它会不断为k+1k+2、...、n 级别创建更多循环,即使我们已经知道这一切都是徒劳的,因为我们只是曾经需要长度的组合k)。

这就是它的要点,尽管您的代码不会在最深级别构建curr,如上所示,而是通过在每个(jth)级别附加i<sub>j</sub>。这就是为什么它需要在从for 循环中附加下一个值之前pop 它,实际上更新jth 在curr 中的位置:

   for i1 in range( 1, n+1):
      curr.append(i1)
      for i2 in range( i1+1, n+1):
         curr.append(i2)
            .........
               for ik in range( ik1+1, n+1):
                  curr.append(ik)
                  output.append( curr )
                  curr.pop()
            .........
         curr.pop()
      curr.pop()

直接改变curr中的jth的值也可以达到同样的效果。您需要为此预先创建k-long curr(最初填充一些不重要的值),并为此引入一个额外的计数器:

   for i1 in range( 1, n+1):
      curr[0] = i1                             # j == 0
      for i2 in range( i1+1, n+1):
         curr[1] = i2                          # j == 1
            .........
               for ik in range( ik1+1, n+1):
                  curr[k-1] = ik               # j == k-1
                  output.append( curr )

这就是(按时间顺序)回溯的全部内容。只是嵌套循环,每个循环都保持其状态,准备好在控件返回(回溯)到它时尝试另一个替代方案。

另外,这就是 “monads” 的全部意义所在。只是广义嵌套循环,协同工作以从最深一个内部产生输出,无论有多少上面有层次。

【讨论】:

    猜你喜欢
    • 2019-01-24
    • 2022-01-07
    • 2013-04-23
    • 2011-10-22
    • 2020-02-14
    • 2023-03-12
    • 2022-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多