路径总和I
1.两种解法
递归–前序遍历
要想求一条路径上的所有节点的和,使用递归的话要进行回溯;递归方法采用深度优先搜索,至于使用前序遍历、中序遍历还是后序遍历都无所谓,因为没有对中间节点的处理。具体递归思路如图所示:
代码如下:
def hasPathSum(root, targetSum):
sum = 0 # 记录路径之和;注意:这里所有路径的和用一个sum就够了,因为使用了回溯
if root: # 如果节点为空
sum += root.val
else: # 如果节点不为空
return False
# 前序遍历
def traversal(root,sum,targetSum):
if not root.left and not root.right: # 如果该节点是叶子节点
if sum == targetSum: # 判断到叶子节点的和是否等于targetSum
return True
if root.left: # 如果该节点有左孩子
sum += root.left.val # sum加上左孩子的值
if traversal(root.left,sum,targetSum): # 递归
return True
sum -= root.left.val # 回溯,sum减去左孩子的值
if root.right: # 如果该节点有右孩子
sum += root.right.val # sum加上左孩子的值
if traversal(root.right,sum,targetSum): # 递归
return True
sum -= root.right.val # 回溯,sum减去左孩子的值
return False # 如果左孩子和右孩子的路径和都不等于targetSum,则返回False
return traversal(root,sum,targetSum) # 返回前序遍历返回的值
迭代–前序遍历
如果想用迭代的方法解决这道题,我们想要得到从根节点到该节点的路径和,需要该节点的值和从根节点到该节点的路径和。在节点进栈时,除了要记录该节点的值,还要记录从根节点到该节点路径和,为了做到这一点,我们在每次节点进栈的时候,就让路径和+该节点的值一起进栈。
def haspathsum(self, root: treenode, targetsum: int) -> bool:
if not root:
return False
stack = [] # [(当前节点,路径数值), ...]
stack.append((root, root.val)) # root进栈,路径和(0) + 该节点的值(root.val)也进栈
while stack:
cur_node, path_sum = stack.pop() # 当前节点,路径和
# 如果当前节点是叶子节点并且路径和等于targetSum,就返回True
if not cur_node.left and not cur_node.right and path_sum == targetsum:
return True
# 先进栈右节点,出栈的时候才是右节点后出,这样才能中左右(前序遍历的顺序)
# 如果有右孩子,则让右孩子入栈,并且路径和(path_sum) + 该节点的值(cur_node.right.val)入栈
if cur_node.right:
stack.append((cur_node.right, path_sum + cur_node.right.val))
# 如果有左孩子,则让左孩子入栈,并且路径和(path_sum) + 该节点的值(cur_node.left.val)入栈
if cur_node.left:
stack.append((cur_node.left, path_sum + cur_node.left.val))
return False # 如果没找到,则返回False
2.总结
算法
递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。
路径总和II
1.解法
递归法–前序遍历
这道题就是在上面那道题的基础上增加了一个把所有路径都找出来的操;所以我们定义一个path用来保存从根节点到该节点的路径,然后一起更改和回溯path、sum即可。
def pathSum(root, targetSum):
path = [] # 记录路径;注意:这里所有路径的和用一个path就够了,因为使用了回溯
result_path = []
sum = 0 # 记录路径之和;注意:这里所有路径的和用一个sum就够了,因为使用了回溯
if root:
sum += root.val
path.append(root.val)
else:
return []
def traversal(root,path,sum,targetSum):
if not root.left and not root.right:
if sum == targetSum:
spath = path.copy() # 注意一定要有这个操作,因为在后面回溯的时候会更改path,这样会导致result_path里面的值也会更改
result_path.append(spath)
if root.left:
sum += root.left.val
path.append(root.left.val)
traversal(root.left,path,sum,targetSum)
# 回溯
sum -= root.left.val
path.pop()
if root.right:
sum += root.right.val
path.append(root.right.val)
traversal(root.right, path, sum, targetSum)
# 回溯
sum -= root.right.val
path.pop()
traversal(root, path, sum, targetSum)
return result_path
这道题也可以用迭代法求解,和上面的那个方法差不多,这里就不给出代码了,供读者自己思考。
2.总结
python
List的=、浅拷贝和深拷贝
-
=:如果用=进行直接赋值,是非拷贝方式。改变等号左右任何一个变量的值都会同时影响这两个变量。
-
浅拷贝:使用copy()、列表生成式、for循环遍历、切片都只会对List的第一层进行深拷贝,而内嵌的List拷贝的是地址,是浅拷贝。
例如:
old = [1,[1,2,3],3] new = old.copy()
new复制了第一个元素:1、第二个元素:[1,2,3]的地址、第三个元素:3
-
深拷贝:使用deepcopy(),无论List嵌套了多少层,无论怎样的形式,得到的新列表都是和原来无关的,这是最安全最清爽最有效的方法。注意:需要import copy
算法
在这道题里,递归并不需要有返回值,因为每次找到一个path,就直接加入到result_path中即可;不需要返回后对递归的返回值进行任何处理。而上面那个题,我们需要判断该节点下,从根节点到该节点的路径和是否符合要求,即需要进行以下操作:
if traversal(root.left,sum,targetSum): # 递归
return True
对traversal(root.left,sum,targetSum)的返回值需要一个判断,所以我们需要返回值。