老规矩层序先秒了:
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
res , queue = [] , collections.deque([root])
while queue:
temp = []
for _ in range(len(queue)):
node = queue.popleft()
temp.append(node.val)
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
res.append(temp)
return res[-1][0]
递归法:
本题要找的是最底层、最左边的节点,不能一直往左子树的方向去遍历,因为最左边,可能不是最后一行!
所以正确的思路是,遍历的过程中,优先遍历左子树,当发现左子树没有的时候,再遍历右子树,此时右子树的叶子节点就是最后一层的最左边了(有点绕)。
综上,本题关键就在于求深度最大的叶子节点。
class Solution:
def findBottomLeftValue(self, root: TreeNode) -> int:
self.max_depth = float('-inf')
self.result = None
self.traversal(root, 0)
return self.result
def traversal(self, node, depth):
if not node.left and not node.right:
if depth > self.max_depth:
self.max_depth = depth
self.result = node.val
return
if node.left:
depth += 1
self.traversal(node.left, depth)
depth -= 1
if node.right:
depth += 1
self.traversal(node.right, depth)
depth -= 1
LC 112 路径总和
class Solution:
def func(self , node , target):
#递归出口
if not node.left and not node.right and target == 0:
return True
if not node.left and not node.right and target != 0:
return False
#单层处理逻辑
if node.left:
target -= node.left.val
if self.func(node.left , target) == True: return True
target += node.left.val
if node.right:
target -= node.right.val
if self.func(node.right , target) == True: return True
target += node.right.val
return False
def hasPathSum(self, root: Optional[TreeNode], targetSum: int):
if not root:
return False
return self.func(root , targetSum - root.val)
再次熟悉了递归三部曲:第一,确定传入参数及返回值;第二,递归出口;第三,单层处理逻辑。本题中传入参数是根节点及target,返回的是T or F,我们的想法是逢节点便用target减去节点值,直到叶子节点,若target为0,则返回T,否则F,所以传入参数时需要手动减去根节点的值,再往下遍历;
递归出口:
当遍历到叶子节点,即为递归出口。
单层处理逻辑:
思考单层处理逻辑,可以就当作在处理根节点的下一个节点。那么首先需要判断是否为空,不为空才有单层处理逻辑。然后,进入这个节点,target减去节点值(node.val),再调用函数本身,去检测以当前节点为根节点的所有左子树,是否存在路径和为target - node.val。若存在,直接返回True,若不存在,就吧node.val加回来,继续往右子树遍历。加回来就是回溯的过程,是为了保障target不会被一直减
LC 113 路径总和Ⅱ
思路差不多,由于要遍历完,所以递归调用时,不需要返回值。
class Solution:
def __init__(self):
self.result = []
self.path = []
def traversal(self , cur , count):
if not cur.left and not cur.right and count == 0:
self.result.append(self.path)
if cur.left: # 左 (空节点不遍历)
self.path.append(cur.left.val)
count -= cur.left.val
self.traversal(cur.left, count) # 递归
count += cur.left.val # 回溯
self.path.pop() # 回溯
if cur.right: # 右 (空节点不遍历)
self.path.append(cur.right.val)
count -= cur.right.val
self.traversal(cur.right, count) # 递归
count += cur.right.val # 回溯
self.path.pop() # 回溯
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
if not root:
return self.result
self.path.append(root.val) # 把根节点放进路径
self.traversal(root, targetSum - root.val)
return self.result
这么写,就错了!此处有一个极其隐蔽的坑,涉及到python当中列表是可变的特性,问题出现在这句话:
if not cur.left and not cur.right and count == 0:
self.result.append(self.path)
此处我们设想的是:当遇到叶子节点且满足条件时,我们将这个满足条件的路径添加到result中,但是,由于path这个列表是可变的,后面一旦修改了path,这里的结果也会跟着变,所以得不到想要的结果!
正确的做法是先copy一个备份出来,再把备份添加到结果中
if not cur.left and not cur.right and count == 0:
temp = self.path.copy()
self.result.append(temp)
此处也给我们提醒了,遇到列表的时候,多思考下其可变的特性,尤其是在返回答案的时候。