使用python递归返回列表列表

时间:2018-05-16 17:24:06

标签: python-3.x recursion

我正在刷新递归问题,并且当问题语句要求打印值时没有问题(例如,BST遍历打印节点值的地方),并且当问题要求返回值列表时几乎没有问题(返回树中2个节点之间的路径)但是当存在多个答案时涉及到要返回的多个列表(或单个2D列表)时出现问题。例如,问题是要求我返回一个孩子可以到达楼梯顶部的方式,假设它可以一次跳过1,2或3个步骤。这没问题,可以在下面解决

def step_helper(steps):
    if(steps == 0):
        return 1
    elif(steps < 0):
        return 0
    else:
        return step_helper(steps-1) +
             step_helper(steps-2) + 
             step_helper(steps-3)

def find_num_ways(steps):
    count = step_helper(steps)
    print(count)

find_num_ways(10)

同样,如果我需要从BST中的两个节点返回路径,则返回1个列表是没有问题的

def another_path_helper(self, n1, n2):
    if(n1 == None):
        return [None]
    elif(n1 == n2):
        return [n1.data]
    elif(n1.data > n2.data):
        return [n1.data] + self.another_path_helper(n1.left, n2)
    elif(n1.data < n2.data):
        return [n1.data] + self.another_path_helper(n1.right, n2)
    else:
        return None

def another_path(self, n1, n2):
    path = self.another_path_helper(n1, n2)
    if(None in path):
        return None
    else:
        return path

但是,我对如何返回列表列表毫无头绪。在子步骤示例中,我不是返回一个孩子爬楼梯的方式,而是如何返回一个路径列表,这将是一个二维列表,其中每个条目都是一个步骤列表从下到上?理想情况下,我不需要将列表作为参数传递给我的递归函数,因为我被告知将可变对象传递给递归函数是一种不好的做法,与使用静态计数器没什么不同。

1 个答案:

答案 0 :(得分:2)

将列表作为参数传递给递归函数并且不修改它绝对没有错。事实上,这样做可以解决问题。

考虑问题的一个小版本:只需3个步骤。你在楼梯的底部。您可以采取一步,两步或三步。然后你有3个子问题需要解决:

  • 所有解决方案均以[1]路径开头,再执行2个步骤。
  • 所有解决方案都以[2]路径开头,再增加一步。
  • 所有解决方案都以路径[3]开头,再进行0步。

看起来是递归解决方案的良好开端。

让我们只关注这些子问题中的第一个。您的路径为[1],您还需要执行2个步骤。从这里,您可以采取一步,两步或三步。你又有3个子问题:

  • 所有解决方案都以[1,1]路径开头,再增加一步。
  • 所有解决方案均以[1,2]路径开头,再增加0步。
  • 所有解决方案均以[1,3]路径开头,后续步骤为-1。

第一个子子问题需要更多工作......另一个递归调用,应该返回[[1,1,1]]。第二个子问题应该只返回我们到达此处的路径:[[1,2]]。最后一个子子问题应该不返回任何解决方案:[]。我们将这些解决方案[[1,1,1]] + [[1,2]] + []一起添加到[[1,1,1],[1,2]],然后返回。

备份,第二个子问题,&#34;从[2]的路径开始,再进行1步&#34;应该返回[[2,1]]作为解决方案集。第三个子问题,&#34;从[3]的路径开始,进行0个额外的步骤&#34;应该返回[[3]]。将这些解决方案与[[1,1,1],[1,2]]一起添加可提供完整的解决方案集:[[1,1,1],[1,2],[2,1],[3]]

代码:

def find_paths(total):

   def helper(path, remaining):
      paths = []
      if remaining == 0:
         paths.append(path)
      elif remaining > 0:
         for step in range(1,3+1):
            paths.extend( helper(path + [step], remaining - step))
      return paths

   return helper([], total)

print(find_paths(3))

正如预期的那样,输出是:

  

[[1,1,1],[1,2],[2,1],[3]]

当然,您不必将当前的步骤列表path传递给递归调用。您可以改为从当前步骤到楼梯顶部询问所有路径,并为刚刚采取的步骤添加前缀。在这种情况下,我们甚至不需要帮助者:

def find_paths(remaining):

   paths = []
   if remaining == 0:
      paths.append([])
   for step in range(1,3+1):
       if step <= remaining:
          subpaths = find_paths(remaining - step)
          for subpath in subpaths:
             paths.append([step] + subpath)

   return paths

print(find_paths(4))

正如预期的那样,输出是:

  

[[1,1,1,1],[1,1,2],[1,2,1],[1,3],[2,1,1],[2,2], [3,1]]

应该注意的是find_paths(2)将被调用 - 并且在提升前两个步骤之后将返回相同的子路径[[1,1], [2]],一次一个地使用路径{{1或者作为路径[1,1]的两个步骤跳转。由于它返回相同的值,而不是从该点重新计算所有子路径,我们可以缓存结果,并在后续步骤中重用该值。

[2]
  

274
  CacheInfo(hits = 17,misses = 11,maxsize = 128,currsize = 11)

如果将缓存大小设置为零from functools import lru_cache @lru_cache() def find_paths(remaining): paths = [] if remaining == 0: paths.append([]) for step in range(1,3+1): if step <= remaining: subpaths = find_paths(remaining - step) for subpath in subpaths: paths.append([step] + subpath) return paths paths = find_paths(10) print(len(paths)) print(find_paths.cache_info()) ,则可以看到@lru_cache(maxsize=0)函数在问题过程中被调用600次:find_paths()。启用缓存后,仅调用28次,仅执行11次; 17次,先前存储的结果立即返回,这可以节省相当多。