我的个人微信公众号:Microstrong
微信公众号ID:MicrostrongAI
公众号介绍:Microstrong(小强)同学主要研究机器学习、深度学习、计算机视觉、智能对话系统相关内容,分享在学习过程中的读书笔记!期待您的关注,欢迎一起学习交流进步!
知乎专栏:https://zhuanlan.zhihu.com/Microstrong
Github:https://github.com/Microstrong0305
个人博客:https://blog.csdn.net/program_developer
题目链接:
题目描述:
解题思路:
例如, 输入图1中的二叉树和整数22,则打印出两条路径,第一条路径包含节点10、12,第二条路径包含节点10、5和7。
一般的数据结构和算法的教材都没有介绍树的路径, 因此对大多数应聘者而言,这是一个新概念,也就很难一下子想出完整的解题思路。 这时候我们可以试着从一两个具体的例子入手,找到规律。
以图1中的二叉树作为例子来分析。 由于路径是从根节点出发到叶节点,也就是说路径总是以根节点为起始点,因此我们首先需要遍历根节点。在树的前序、中序、后序3种遍历方式中,只有前序遍历是首先访问根节点的。
按照前序遍历的顺序遍历图1中的二叉树,在访问节点10之后,就会访问节点5。从二叉树节点的定义可以看出,在本题的二叉树节点中没有指向父节点的指针,当访问节点5的时候,我们是不知道前面经过了哪些节点的, 除非我们把经过的路径上的节点保存下来。每访问一个结点,我们都把当前节点添加到路径中去。当到达节点5时, 路径中包含两个节点,它们的值分别是10和5。接下来遍历到节点4,我们把这个节点也添加到路径中。这时候已经到达叶节点,但路径上3个节点的值之和是19。这个 和不等于输入的值22,因此不是符合要求的路径。
我们接着要遍历其他的节点。在遍历下一个节点之前,先要从节点4回到节点5,再去遍历节点5的右子节点7。值得注意的是,当回到节点5的时候,由于节点4已经不在前往节点7的路径上了,所以我们需要把节点4从路径中删除。接下来访问节点7的时候,再把该节点添加到路径中。此时路径中的3个节点10、5、7之和刚好是22,是一条符合要求的路径。
我们最后要遍历的是节点12。在遍历这个节点之前,需要先经过节点5回到节点10。同样,每次当从子节点回到父节点的时候,我们都需要在路径上删除子节点。最后在从节点10到达节点12的时候,路径上的两个节点的值之和也是22,因此,这也是一条符合要求的路径。
我们可以用表1总结上述分析过程。
步骤 | 操作 | 是否叶节点 | 路径 | 路径节点值的和 |
1 | 访问节点10 | 否 | 节点10 | 10 |
2 | 访问节点5 | 否 | 节点10、节点5 | 15 |
3 | 访问节点4 | 是 | 节点10、节点5、节点4 | 19 |
4 | 访问节点5 | 节点10、节点5 | 15 | |
5 | 访问节点7 | 是 | 节点10、节点5、节点7 | 22 |
6 | 访问节点5 | 节点10、节点5 | 15 | |
7 | 访问节点10 | 节点10 | 10 | |
8 | 访问节点12 | 是 | 节点10、节点12 | 22 |
分析完前面具体的例子之后,我们就找到了一些规律。当用前序遍历的方式访问到某一节点时,我们把该节点添加到路径上, 并累加该节点的值。如果该节点为叶节点,并且路径中节点值的和刚好等于输入的整数,则当前路径符合要求,我们把它打印出来。如果当前节点不是叶节点,则继续访问它的子节点。当前节点访问结束后,递归函数将自动回到它的父节点。因此,我们在函数退出之前要在路径上删除当前节点并减去当前节点的值,以确保返回父节点时路径刚好是从根节点到父节点。我们不难看出保存路径的数据结构实际上是一个栈,因为路径要与递归调用状态一致,而递归调用的本质就是一个压栈和出栈的过程。
形成了清晰的思路之后,就可以动手写代码了。
已经AC的代码:
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
# 返回二维列表,内部每个列表表示找到的路径
def FindPath(self, root, expectNumber):
if not root:
return []
res = []
path = []
self.subPath(root, res, path, expectNumber, 0)
return res
def subPath(self, root, res, path, expectNumber, currentNum):
currentNum += root.val
# root使用append转换成了列表,因为最后要一个序列,root本身还是树节点结构
path.append(root)
# 判断是不是到叶子节点了,到叶子节点了就要判断值的和是不是相等
if not root.left and not root.right:
if currentNum == expectNumber:
onePath = []
for node in path:
onePath.append(node.val)
res.append(onePath)
if currentNum < expectNumber:
if root.left:
self.subPath(root.left, res, path, expectNumber, currentNum)
if root.right:
self.subPath(root.right,res, path, expectNumber, currentNum)
# 拿到一个正确的路径后要弹出,回到上一次父节点继续递归
path.pop()
if __name__ == "__main__":
sol = Solution()
n1 = TreeNode(10)
n2 = TreeNode(5)
n3 = TreeNode(12)
n4 = TreeNode(4)
n5 = TreeNode(7)
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
print(sol.FindPath(n1, 22))
Reference:
【1】《剑指offer》,何海涛著。