代码随想录算法训练营第十五天(二叉树篇)|513. 找树左下角的值,112. 113. 路经总和

513. 找树左下角的值

题目链接: 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. 路经总和

题目链接: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. 路经总和Ⅱ

题目链接: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单独拿出来,使递归中的它们不断更新。

注意,在使用递归时,那些一直会变化的数是不能在函数里面初始化的。

你可能感兴趣的:(代码随想录训练营,数据结构,python,算法)