代码随想录算法训练营第十四天| 找树左下角的值、路径总和、从中序与后序遍历序列构造二叉树

写代码的第十四天
总的来说一塌糊涂。。。。。写递归写的我想die。。。。。好像有点懂但又没完全懂。。。。。

513. 找树左下角的值

思路(层序)

import collections
class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        queue = collections.deque()
        result = []
        if root == None:
            return 
        queue.append(root)
        while len(queue) != 0:
            res = []
            for _ in range(len(queue)):
                node = queue.popleft()
                res.append(node.val)
                if node.left != None:
                    queue.append(node.left)
                if node.right != None:
                    queue.append(node.right)
            result.append(res[0])
        return result[-1]

思路(递归)

题中要求的是树左下角的值,那么其实就是要知道这棵树的最大深度在哪,最深的地方的最左侧的叶子结点就是我们要找的结点。用递归的方式找最大深度,那么遍历到的最大深度的第一个叶子结点就是我们要的左下角的结点,因为无论是前中后序,在不看中结点的情况下,先遍历的都是左结点。
解决问题1:递归的参数和返回值?参数必须要有结点node,
解决问题2:终止条件?遇到叶子结点就终止。如果node的左右孩子都为空那么就证明遇到了叶子结点,此时让这个叶子结点的深度和当前的最大深度及逆行比较,如果大于最大深度,那么久将这个深度赋值给最大深度,并且此时遇到的叶子结点是当前最大深度的最左侧结点,所以将这个结点的值赋值给最后的结果result。
解决问题3:单次递归的逻辑?其实就是在没遇到叶子结点之前的遍历过程。根据终止条件那里我们需要的数据是深度,所以在单次递归的时候我们要记录的就是深度。我们从根结点开始走一遍这个递归,首先判断根结点的左孩子空不空,不空的话深度就+=1,然后在看根结点的右孩子空不空,不空的话深度就+1,但是这里会出现一个问题!!!!我们一直判断的是根结点的左孩子和右孩子,并且根结点的左右孩子应该是一个高度,所以一共+1就可以了,但上面的步骤我们+1又+1,一共加了2,错了!!!!所以我们应该在判断左孩子之后深度加一之后回溯一下,也就是-1,之后在判断其右孩子,如果还是有那么再加一。
解决问题4:采用前中后那种遍历方式?要找最左侧的结点,所以在左右之间一定要先遍历左,中结点其实在这里影响不大,因为我们要做的是找叶子结点,而叶子结点在遍历中是中结点的左孩子或者右孩子,所以不涉及中间结点的操作,那么前中后序遍历均可。
错误版本一:根据输出结果不论是什么树结果都是null,看了一下traversal函数,如果每一次的调用depth都从零开始,那么每一次无论是从哪个结点开始遍历,深度都从零开始,但我们的需求是希望每次都记录下来这个depth,然后随着遍历深度加一或回溯减一,所以depth应该在函数外面设置,并作为参数传到traversal中,同时还需要注意maxdepth如果也在函数中设置,那么每次都是-1,每个depth都和-1相比,都比-1大,但是实际上我们需要让之后的值和其前一个maxdepth比较,如果大,才进行交换,值存储,所以maxdepth的值也应该设置在函数外。

class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        return self.traversal(root)

    def traversal(self,node):
        maxdepth = -1
        depth = 0
        if node.left == None and node.right == None:
            if depth > maxdepth:
                maxdepth = depth
                result = node.val
            return 
            
        if node.left:
            depth += 1
            self.traversal(node.left)
            depth -= 1
        if node.right:
            depth += 1
            self.traversal(node.right)
            depth -= 1

这个题让我很痛苦的一个地方就是,几个需要改的以及最后的输出值都是设置的全局变量,然后在函数体内部使用这个变量,并修改。我很菜,我理解这个真的有点困难,这是大佬的代码,我只是硬性的理解了一下,算是记了一下,希望下次写能自己写出来。我试了一下在traversal函数中返回值,但是出问题的是你不知道当前的self.result = node.val究竟是不是最后要的那个值,如果你return了,但是不是最后的输出值,那就错了,所以还只能用外部的全局变量改,啊啊啊啊啊我的脑子啊啊啊啊啊。
正确代码

class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        self.maxdepth = -1
        self.result = None
        self.traversal(root,0)
        return self.result

    def traversal(self,node,depth):
        if node.left == None and node.right == None:
            if depth > self.maxdepth:
                self.maxdepth = 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

112. 路径总和

思路(递归)

这道题和昨天做的求二叉树的全部路径有点像,所以我的做法是把二叉树的左右路径都遍历出来,求出每条路径的和加入到一个列表中,然后遍历这个列表,如果有和目标值相等的那就返回true吗,否则就是false。
在这个题里面我也尝试用了一下上一道题用到的全局变量,因为在本题中,我想要最后的result存储的是每一条路径的和,所以可以在函数体中使用全局变量,每计算一个sums出来,就将其append进入result,这样在外层函数的selfresult中直接就可以保存每条路径的结果了。
解决问题1:递归参数是什么,返回值又是什么?一定要有的参数是结点,我们传入的是root,但是这只是一个参数,写node也行,只要代表的是结点就可以;其次和上题一样,我们需要一个每次递归都要保存好上一次的路径然后在进行下一次递归的路径path;最后的result可以加也可以不加;对于返回值来说,在递归这里面是有没有返回值都行的,因为我们最后需要的是result这个列表,但是这个列表如果你没放在函数参数中,那么就使用了全局变量,直接在函数体外面每次都会更新存储,不需要一个返回值记录,如果你没用全局变量,那么这个result就直接在参数中,每次也会更新,所以也不需要。(我是这样理解的),写了也不会错。
解决问题2:终止条件?我们要的是二叉树的所有路径,所以知道遍历到叶子结点的时候才证明这一条路径结束了,所以终止条件就是这个结点是叶子结点了。
解决问题3:单次递归逻辑?其实在没有到叶子结点的时候是怎么遍历的,怎么递归的?如果这个结点是有左孩子的,那么让这个结点的值append进path,然后开始递归,如果这个结点是有右孩子的,那么也要让这个结点的值append进path,但是如果说这个结点左右孩子都存在,那么它需要同时在path中append其左右孩子的值,这时候就出问题了,所以当我们判断完左孩子或者右孩子的时候,一定要回溯到上一层,这样才算是从这个结点本身在继续找路径。
错误版本一:错误原因是在这段代码中,我们是判断了这个结点的左右孩子是否存在,如果存在了就将这个左右孩子的值append进path中,但是这个结点我们没做任何操作。其实我们要做的一直都是对当前结点进行操作,让当前结点append进path,然后判断这个结点的左右孩子是否存在!!!!

class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        result = []
        path = []
        if root == None:
            return False
        result = self.traversal(root,path,result)
        for i in range(len(result)):
            if result[i] == targetSum:
                return True
        return False


    def traversal(self,node,path,result):
        if node.left == None and node.right == None:
            sums = sum(path)
            result.append(sums)
        if node.left:
            path.append(node.left.val)
            self.traversal(node.left,path,result)
            path.pop()
        if node.right:
            path.append(node.right.val)
            self.traversal(node.right,path,result)
            path.pop()
        return result

在这里插入图片描述
正确代码

class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        self.result = []
        path = []
        if root == None:
            return False
        self.traversal(root,path)
        for i in range(len(self.result)):
            if self.result[i] == targetSum:
                return True
        return False


    def traversal(self,node,path):
        path.append(node.val)
        if node.left == None and node.right == None:
            sums = sum(path)
            self.result.append(sums)
        if node.left:
            self.traversal(node.left,path)
            path.pop()
        if node.right:
            self.traversal(node.right,path)
            path.pop()

不用全局变量也行。

class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        result = []
        path = []
        if root == None:
            return False
        self.traversal(root,path,result)
        for i in range(len(result)):
            if result[i] == targetSum:
                return True
        return False


    def traversal(self,node,path,result):
        path.append(node.val)
        if node.left == None and node.right == None:
            sums = sum(path)
            result.append(sums)
        if node.left:
            self.traversal(node.left,path,result)
            path.pop()
        if node.right:
            self.traversal(node.right,path,result)
            path.pop()

思路(递归,不遍历所有结点)

遇到路径和与target值相等就返回true。
解决问题1:递归参数是什么,返回值又是什么?一定要有的参数是结点,我们传入的是root,但是这只是一个参数,写node也行,只要代表的是结点就可以;其次我们需要一个计数器,用这个计数器去计算路径的值是否和target相等。我们这里不涉及到对哪个值作处理,所以不需要返回值。
解决问题2:终止条件?我们要的是判断这个路径的值是否和计数器是相等的,我们不将计数器初始化为0因为这样后续我们还要把路径上结点的值相加在和target比较,我们直接把计数器设置为target-root.val的值(如果直接赋值为target,那么当遍历到叶子结点的时候),这样每遍历一个结点就减去一个结点的值。所以当这个结点是叶子结点并且计数器为0时返回true,当其为叶子结点但是计数器不为零时返回false。
解决问题3:单次递归逻辑?其实在没有到叶子结点的时候是怎么遍历的,怎么递归的?如果这个点结点有左孩子,那么计数器减去当前结点的数值,然后 进行递归,如果有右孩子,计算器减去当前结点的数值,但是需要注意的地方和上面一样,一定要回溯回去,在判断!!!!
错误第一版:第一处错误在于终止条件不全,我们只判断了叶子结点并且count为零时返回true,但是还有一种情况当遍历到叶子结点了,但是count不为零的时候呢,此时应该返回false。第二处错误我们在每一次递归调用的时候都减去当前结点的值,这样回导致重复减同一个结点的值(比如说就看根结点这里,第一次使用traversel,减去了rootval,此时root有left结点,count继续减去left结点的val,然后继续递归调用traversal,调用时又减去rootleft的val,那么这个结点被减了两次)。

class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if root == None:
            return False
        return self.traversal(root,targetSum)
        


    def traversal(self,node,count):
        count -= node.val
        if node.left == None and node.right == None and count == 0:
            return True
        if node.left:
            count -= node.left.val
            self.traversal(node.left,count)
            count += node.left.val
        if node.right:
            count -= node.right.val
            self.traversal(node.right,count)
            count += node.right.val
        return False

在这里插入图片描述
错误第二版:在if node.left:和if node.right:的遍历中,我们需要考虑一个问题,就是当其中的某一次递归时已经出现满足条件的情况,那我们就不需要继续走下面的代码,直接返回true即可,如果说不返回,而是继续执行的话,很有可能在后面回出现不满足的情况,这样到最后的终止条件那里,就很有可能是false。

class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if root == None:
            return False
        return self.traversal(root,targetSum-root.val)
        
    def traversal(self,node,count):
        #count -= node.val
        if node.left == None and node.right == None and count == 0:
            return True
        if not node.left and not node.right: # 遇到叶子节点直接返回
            return False
        if node.left:
            count -= node.left.val
            self.traversal(node.left,count)
            count += node.left.val
        if node.right:
            count -= node.right.val
            self.traversal(node.right,count)
            count += node.right.val
        return False

在这里插入图片描述
正确代码

class Solution:
    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if root == None:
            return False
        return self.traversal(root,targetSum-root.val)
       
    def traversal(self,node,count):
        #count -= node.val
        if node.left == None and node.right == None and count == 0:
            return True
        if not node.left and not node.right: # 遇到叶子节点直接返回
            return False
        if node.left:
            count -= node.left.val
            if self.traversal(node.left,count):
                return True
            count += node.left.val
        if node.right:
            count -= node.right.val
            if self.traversal(node.right,count):
                return True
            count += node.right.val
        return False

113. 路径总和 II

思路(递归)

和上面是一样的思路
错误第一版:可以看出是result出现了问题。result是用来存储路径的,也就是说在traversal中result值就没传进去,可以定位到result.append(path)。
我查了一下错误原因是:当你将 path 添加到 result 中时,实际上将的是 path 的引用,而不是 path 的副本。这样,在后续的递归调用中,对 path 的修改也会影响到已添加到 result 中的路径。为了解决这个问题,需要将 path 添加到 result 时创建一个副本,而不是直接将 path 添加进去。(虽然改对了,但是我想不通啊啊啊啊啊啊,明明之前做的二叉树全部路径的题就是这么写的,没涉及到副本什么的,凭什么这里面就不对啊啊啊啊。)

class Solution:
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        result = []
        path = []
        res = []
        out = []
        if root == None:
            return 
        self.traversal(root,path,result,res)
        for i in range(len(res)):
            if res[i] == targetSum:
                out.append(result[i])
        return out

    def traversal(self,node,path,result,res):
        path.append(node.val)
        if node.left == None and node.right == None:
            result.append(path)
            sums = sum(path)
            res.append(sums)
        if node.left:
            self.traversal(node.left,path,result,res)
            path.pop()
        if node.right:
            self.traversal(node.right,path,result,res)
            path.pop()

在这里插入图片描述
正确代码

class Solution:
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        result = []
        path = []
        res = []
        out = []
        if root == None:
            return []
        self.traversal(root,path,result,res)
        for i in range(len(res)):
            if res[i] == targetSum:
                out.append(result[i])
        return out

    def traversal(self,node,path,result,res):
        path.append(node.val)
        if node.left == None and node.right == None:
            result.append(path.copy())
            sums = sum(path)
            res.append(sums)
        if node.left:
            self.traversal(node.left,path,result,res)
            path.pop()
        if node.right:
            self.traversal(node.right,path,result,res)
            path.pop()

思路(使用count计数器)

硬跟着题解的代码理解,我脑子真的不够用了好像啊啊啊啊啊啊,为什么我写不出来递归啊啊啊啊啊啊啊啊啊!!!
在这思路中,下面的traversal函数的主要改变就是需要存储path,在这个过程中我总是直接append(path),但是path的类型不是list,如果不做修改类型会被后续的代码改变,所以使用这个self.result.append(list(self.path))。下面就是递归和回溯的部分,上一道题我们直接append node的val是不对的,在这里居然就self.path.append(node.left.val),因为我们在主函数中进行了self.path.append(root.val),所以没有丢结点。

class Solution:
    def __init__(self):
        self.result = []
        self.path = []

    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        if root == None:
            return []
        self.path.append(root.val)
        self.traversal(root,targetSum-root.val)
        return self.result
        
    def traversal(self,node,count):
        if node.left == None and node.right == None and count == 0:
            self.result.append(list(self.path))
            return 
        if not node.left and not node.right: # 遇到叶子节点直接返回
            return 
        if node.left:
            self.path.append(node.left.val)
            count -= node.left.val
            self.traversal(node.left,count)
            self.path.pop()
            count += node.left.val
        if node.right:
            self.path.append(node.right.val)
            count -= node.right.val
            self.traversal(node.right,count)
            self.path.pop()
            count += node.right.val
        return

106. 从中序与后序遍历序列构造二叉树

思路

后序遍历的最后一个数一定是跟结点,然后根据这个数去中序里面找跟结点,左边就是左子树,右边就是右子树,根据这个信息再回到后序遍历中,进行切割左子树和右子树,然后在递归着看这两个子树的最后一个数,一定是这个子树的根结点,然后再回到中序遍历中继续切割。

class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
        if len(postorder) == 0:
            return 
        rootval = postorder[-1]
        root = TreeNode(rootval)
        index = inorder.index(rootval)
        inorder_left = inorder[:index]
        inorder_right = inorder[index + 1:]
        postorder_left = postorder[:len(inorder_left)]
        postorder_right = postorder[len(inorder_left):len(postorder)-1]
        self.buildTree(inorder_left,postorder_left)
        self.buildTree(inorder_right,postorder_right)
        return root

正确代码

class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
        if len(postorder) == 0:
            return 
        rootval = postorder[-1]
        root = TreeNode(rootval)
        index = inorder.index(rootval)
        inorder_left = inorder[:index]
        inorder_right = inorder[index + 1:]
        postorder_left = postorder[:len(inorder_left)]
        postorder_right = postorder[len(inorder_left):len(postorder)-1]
        root.left = self.buildTree(inorder_left,postorder_left)
        root.right = self.buildTree(inorder_right,postorder_right)
        return root

105. 从前序与中序遍历序列构造二叉树

思路

根据前序遍历的第一个数一定是跟结点,然后在中序中找到这个值,左边就是左子树,右边就是右子树,然后在继续递归左子树和右子树,找跟结点。
错误第一版:错误原因 inorder 列表与 preorder 列表不一致,发现preorder_left = preorder[1:len(inorder_left)]和preorder_right = preorder[len(inorder_left):]错了,列表长度不对。应该是len(inorder_left)+1,这样的列表切片才和inorder的长度一致。

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if len(preorder) == 0:
            return 
        rootval = preorder[0]
        root = TreeNode(rootval)
        index = inorder.index(rootval)
        inorder_left = inorder[:index]
        inorder_right = inorder[index+1:]
        preorder_left = preorder[1:len(inorder_left)]
        preorder_right = preorder[len(inorder_left):]
        root.left = self.buildTree(preorder_left,inorder_left)
        root.right = self.buildTree(preorder_right,inorder_right)
        return root

在这里插入图片描述

正确代码

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if len(preorder) == 0:
            return 
        rootval = preorder[0]
        root = TreeNode(rootval)
        index = inorder.index(rootval)
        inorder_left = inorder[:index]
        inorder_right = inorder[index+1:]
        preorder_left = preorder[1:len(inorder_left)+1]
        preorder_right = preorder[len(inorder_left)+1:]
        root.left = self.buildTree(preorder_left,inorder_left)
        root.right = self.buildTree(preorder_right,inorder_right)
        return root
  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值