我正在刷新递归问题,并且当问题语句要求打印值时没有问题(例如,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
但是,我对如何返回列表列表毫无头绪。在子步骤示例中,我不是返回一个孩子爬楼梯的方式,而是如何返回一个路径列表,这将是一个二维列表,其中每个条目都是一个步骤列表从下到上?理想情况下,我不需要将列表作为参数传递给我的递归函数,因为我被告知将可变对象传递给递归函数是一种不好的做法,与使用静态计数器没什么不同。
答案 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次,先前存储的结果立即返回,这可以节省相当多。