题目链接: 513. 找树左下角的值 - 力扣(LeetCode)
思路和昨天的层序遍历差不多,尤其是二叉树的右式图。我们知道坐下角一定是在某一层的最左边,也就是第一个被遍历到的,但并不能直接到达想要的最后一层,因此就几下每一层最左边的元素,直到某层之后再无子嗣,就返回记录下的最后一个元素的值。
class Solution(object):
def findBottomLeftValue(self, root):
if root == None:
return []
bottomLeft = []
queue = collections.deque([root])
while queue:
size = len(queue)
for i in range(size):
cur = queue.popleft()
if i == 0:
bottomLeft.append(cur.val)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
return bottomLeft[-1]
代码随想录里也讲了递归的方法。真的是“一入递归深似海”啊!看着代码和解析,我应是懂了大概,但要是遇到类似的题完全让我自己写,估计也只能仰天长叹。
class Solution(object):
def findBottomLeftValue(self, root):
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
if node.left:
depth += 1
self.traversal(node.left, depth)
depth -= 1
if node.right:
depth += 1
self.traversal(node.right, depth)
depth -= 1
下面是我个人的理解,欢迎讨论和指教:本体递归的思路是通过回溯深度来记录node所在的层数。以下面的二叉树为例:
我们先初始化最大深度为一个很小的数,得到的结果为None,然后运行traversal函数,从root,节点1出发,设置深度为0。
先执行上半部分的代码:
if node.left:
depth += 1
self.traversal(node.left, depth)
发现节点1存在左节点,节点2,于是深度加一变为1,跳到节点2,继续重新运行traversal函数,就回到了开头:
if not node.left and not node.right:
if depth > self.max_depth:
self.max_depth = depth
self.result = node.val
我们发现节点2没有子嗣,符合if的条件,又因为深度1大于max_depth(负无穷),就记录下当前的深度1和节点2,接下来又来到了:
if node.left:
depth += 1
self.traversal(node.left, depth)
depth -= 1
if node.right:
depth += 1
self.traversal(node.right, depth)
depth -= 1
因为节点2没有左节点了,根据递归的思想,上面的
self.traversal(node.left, depth)
执行完毕,node自动返回上一层,到节点1,但深度不会自动返回,就需要人工减1,变为0:
depth -= 1
这时候,因为节点1的左节点已经做过遍历了,于是来到它的右节点3,深度加1变为2。然后以此类推,到左节点4,深度变为3,因为4无子嗣,且所在depth高于max_depth,1,于是result右节点2替换成节点4,max_depth变为3。随后由节点4回到3又到节点5,节点5无子嗣,但所在深度(3)并不大于max_depth,所以不记录节点。至此,整棵树遍历结束,返回最终记录的节点4.
上一题我以为我真的认识了递归,结果,尝试做路经总和,又陷入了沉思......
我卡在不知道在一条路径走完后,要往上返回多少个节点到达两条路径的交叉点以继续走另一条路径,其实还是没有领悟递归的奥义。明明直到这题和找树左下脚的思路是相似的,那为什么上一个节点我能说服自己节点的深度能一个个回溯,这一题就不行了?
事实上,当我们在函数里自己调用自己时,针对的是每一个节点,每个节点执行完代码后,都要回溯一下深度。如果调用的函数中又调用了一次自己,那么在最里层的函数执行完,回溯之后,它外面那一层的函数此时也执行完毕,于是再回溯了一次,这样就总共回溯了两次。
搞明白这一点,再想代码,就好办了很多。
题目链接:112. 路径总和 - 力扣(LeetCode)
class Solution:
def traversal(self, cur, count) :
if not cur.left and not cur.right and count == 0: # 遇到叶子节点,并且计数为0
return True
if not cur.left and not cur.right: # 遇到叶子节点直接返回
return False
if cur.left: # 左
count -= cur.left.val
result = self.traversal(cur.left, count)
if result: # 递归,处理节点
return True
count += cur.left.val # 回溯,撤销处理结果
if cur.right: # 右
count -= cur.right.val
result = self.traversal(cur.right, count)
if result:# 递归,处理节点
return True
count += cur.right.val # 回溯,撤销处理结果
return False
def hasPathSum(self, root, sum):
if root is None:
return False
return self.traversal(root, sum - root.val)
题目链接:113. 路径总和 II - 力扣(LeetCode)
这题在上一题的基础上要返回所有和为目标值的路径,因此我们需要加一个列表,把当前节点的左右子节点加上去,每次递归结束,再把之前加进去的元素弹出。
我一开始将结果数组answer和每条路径的数组route在traversal函数中设置为空,但这样在自己调用自己后,整个函数重头再来,answer和route又被初始化了,造成答案错误
有两种方法可以解决,一个是把answer和route设置为这个类的属性,在traversal函数中改变这两个数组。
class Solution(object):
def __init__(self):
self.answer= []
self.route = []
def traversal(self, cur, count):
#answer = []
#route = collections.deque([cur])
if not cur.left and not cur.right and count == 0:
self.answer.append(self.route[:])
if not cur.left and not cur.right:
pass
if cur.left:
count -= cur.left.val
self.route.append(cur.left.val)
self.traversal(cur.left, count)
count += cur.left.val
self.route.pop()
if cur.right:
count -= cur.right.val
self.route.append(cur.right.val)
self.traversal(cur.right, count)
count += cur.right.val
self.route.pop()
def pathSum(self, root, targetSum):
#self.answer.clear()
#self.route.clear()
if not root:
return self.answer
self.route.append(root.val) # 把根节点放进路径
self.traversal(root, targetSum - root.val)
return self.answer
另外一种,把answer和route设置为traversal的自变量,每次调用自己时使用的都是更新后的数组。
class Solution:
def traversal(self, cur, count, route, answer):
if not cur.left and not cur.right and count == 0:
answer.append(list(route))
if cur.left:
count -= cur.left.val
route.append(cur.left.val)
self.traversal(cur.left, count, route, answer)
count += cur.left.val
route.pop()
if cur.right:
count -= cur.right.val
route.append(cur.right.val)
self.traversal(cur.right, count, route, answer)
count += cur.right.val
route.pop()
def pathSum(self, root, targetSum):
if not root:
return []
answer = []
route = collections.deque([root.val])
self.traversal(root, targetSum - root.val, route, answer)
return answer
其实这两种方法都是把answer和route单独拿出来,使递归中的它们不断更新。
注意,在使用递归时,那些一直会变化的数是不能在函数里面初始化的。