LeetCode112.Path Sum
LeetCode437.Path Sum III
Given a binary tree and a sum, find all root-to-leaf paths where each path’s sum equals the given sum.
Note: A leaf is a node with no children.
Example:
Given the below binary tree and sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
Return:
[
[5,4,11,2],
[5,8,4,5]
]
分析
同LeetCode112.Path Sum题一样,只不过这一题需要返回的是具体的路径列表。首先建立两个列表,一个作为返回结果列表Path(大P),另一个中间列表path(小p)。
我们很自然会想到在遍历到某一个点的时候需要将其root.val的值加进中间列表,已经在满足条件(即sum值和叶节点两个条件同时满足)时候将当前的path列表添加到返回结果的Path里面。
在这里结合自己的做题心路整理几点注意地方:
- 当遍历到一个不满足条件的叶节点后,需要回溯返回上一个父节点,同时把当前的节点值弹出,也就是递归回溯和pop()操作
- pop操作需要在if条件判断之外,也就是说当有一个满足情况的叶节点被遍历后,在返回之后仍然需要弹出它的值,以便遍历它的兄弟节点或者结束递归。
- 在有满足条件的时候,将当前路径path添加到返回结果Path之中时候,不可直接Path.append(path)。
关于第2和3点的情况我会在下面通过实验情况展示。
Code
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def __init__(self):
self.Path = []
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
if root:
path = []
self.helper(path,root,sum)
return self.Path
def helper(self,path,root,sum):
if root:
path.append(root.val)
if root.val == sum and self.isLeaf(root):
self.Path.append(path)
self.helper(path, root.left, sum-root.val)
self.helper(path, root.right, sum-root.val)
path.pop()
def isLeaf(self, root):
if root:
if root.left is None and root.right is None:
return True
为了能够清楚地展示出path和Path的变化过程,在其变化前后分别加上print函数。
def helper(self, path, root, sum):
if root:
path.append(root.val)
if root.val == sum and self.isLeaf(root):
self.Path.append(path.copy())
print('path = ', path)
print('Path = ', self.Path)
self.helper(path, root.left, sum - root.val)
self.helper(path, root.right, sum - root.val)
path.pop()
print('path = ', path)
运行结果如下:
通过上图我们就可以清楚的看出来path和Path变化过程的
错误情况展示
关于第2点的情况,也就是pop在if…else…循环内的情况
def helper(self,path,root,sum):
if root:
path.append(root.val)
if root.val == sum and self.isLeaf(root):
self.Path.append(path.copy())
else:
self.helper(path, root.left, sum-root.val)
self.helper(path, root.right, sum-root.val)
path.pop()
注意,这里的pop和递归是在else之内的。首先,递归在不在else内无所谓,因为只要不是满足条件的叶节点都会执行else的内容,满足情况的叶节点执行else的内容也是返回None,所以关于两个递归语句并不影响。而对于pop而言,放在else里面,则会在出现满足情况的叶节点时候不再执行else语句,这样就会少一次pop,因此在遍历兄弟节点或者返回上一级的时候,就会多出来一个数。每有一个这样的情况,就会少pop一次,从而导致最终结果的错误。
以这个例子来看,在第二个正确路径被找到前,if语句执行了一次,也就是会少pop一次,因此左边部分的4未能从path中pop出来就被调用到右边部分的递归中去,最终导致8前面多出来一个4。
关于第3点的情况,也就是不用path.copy()而是直接用path对Path进行添加。
def helper(self,path,root,sum):
if root:
path.append(root.val)
if root.val == sum and self.isLeaf(root):
self.Path.append(path)
else:
self.helper(path, root.left, sum-root.val)
self.helper(path, root.right, sum-root.val)
path.pop()
注意,上面的helper
函数只是和正确的函数在self.Path.append(path)
有区别。
这里如果不加path.copy()
的时候就会导致,添加进Path结果中的path和外面继续执行的path仍然是一个对象,也就是说是会继续改变的。这样就会在最后导致所有添加进结果Path列表的path都是一样的,而且在最后的递归全部都结束后中间列表path里面的所有值都被弹出,path和返回结果Path都将是空列表。(具体变化过程可以参见上面的path和Path变化过程图,最后的path是变为空列表的[ ])
而如果用了copy函数后,就相当于复制创建了一个新的对象,和原来的path的存储地址也是不同的,所以不会继续发生改变。
为了更加直观展示,这里附上我在PyCharm里面对这个的单独测试。
list1 = ['Google', 'Runoob', 'Taobao', 'Baidu']
list1.append('Leetcode')
print(id(list1))
print(id(list1.copy()))
print(list1)
L = []
L.append(list1)
print(L)
L1 = []
L1.append(list1.copy())
print(L1)
list1.append('Alibaba')
print(L)
print(L1)
返回结果为:
296565896
296566024
['Google', 'Runoob', 'Taobao', 'Baidu', 'Leetcode']
[['Google', 'Runoob', 'Taobao', 'Baidu', 'Leetcode']]
[['Google', 'Runoob', 'Taobao', 'Baidu', 'Leetcode']]
[['Google', 'Runoob', 'Taobao', 'Baidu', 'Leetcode', 'Alibaba']]
[['Google', 'Runoob', 'Taobao', 'Baidu', 'Leetcode']]
我们可以看到list1和list1.copy()的id地址是不一样的,也即是二者不是一个对象。在通过直接append将list1或list.copy将二者分别加入到L和L1此时发现二者并无区别和list1是一样的。但是当我们继续对list1进行修改后发现,L仍然会发生变化,而L1不再变化。