LeetCode-树

写这一系列的笔记,是为了分类整理LeetCode里面的算法题目,以及涉及到的算法的知识点。

树天然就是一种递归结构,所以与树相关的问题,通常都是以下三步:1、明确递归停止的条件(何时return);2、递归的本质是栈式调用,明确从树的叶子节点到根节点的主要的代码逻辑;3、二叉树分左右两棵子树递归。多叉树for循环递归。在Python中,为了减少参数个数,一般树的递归或者dfs通常会和闭包混合使用,即在解题函数中套用一个backtrack的辅助函数。

以LeetCode热题Hot100为例,来复习一下求解树类问题的套路:

94.二叉树的中序遍历

树的遍历方式分为前序、中序、后序、层序,前面三种指的是根节点的排放位置,例如中序遍历就是递归按照左子树-根节点-右子树的方式进行遍历。于是按照之前的三步走则为:1、递归停止条件,如果当前节点为空,则返回对应的res列表。2、主体代码逻辑:res首先加上左子树中序遍历的列表,再加上根节点的值,最后加上右子树中序遍历的列表。3、左右递归,实际上已经在第2步中完成了。代码如下

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        if not root:
            return res
        res += self.inorderTraversal(root.left)
        res.append(root.val)
        res += self.inorderTraversal(root.right)
        return res

98.验证二叉搜索树

这道题目的递归相对来说要复杂一些,因为二叉搜索树右子树的最小值不一定就是root.right.val,而有可能是root.right.left.let..val,对于左子树亦然。所以我们需要维护两个临时变量,来兜住当前树的最大值和最小值。假如当前树是一棵左子树,那么其最小值可以认为是-inf,最大值认为是root.val。如果当前树是右子树,那么最大值是inf,最小值是root.val。所以继续三步走:1、递归终止条件:当前节点为空,则return True。2、主逻辑,判断当前节点的值是否落在最小最大值的区间里面,如不符合则返回False。3、左右子树递归,更换相应的最小最大值,如不满足则返回False。代码如下:

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        return self.helper(root)
    def helper(self,node,lower=float('-inf'),upper=float('inf')):
        if not node:
            return True
        val = node.val
        if val <= lower or val>= upper:
            return False
        if not self.helper(node.right,val,upper):
            return False
        if not self.helper(node.left,lower,val):
            return False
        return True

101 对称二叉树

大多数和树相关的问题,都没有前序遍历、中序遍历、后序遍历那么简单。因为大多数需要对参数进行改变,例如上一题的二叉搜索树的验证,所以需要设计辅助函数,这一道题的子问题是左右子树是否是镜像对称的。所以很自然地想到需要加一个辅助函数,参数是两个node,以判断这两棵树是否是镜像对称的。

递归肯定是针对辅助函数进行递归,那么继续三步走:1、递归终止条件:两棵树的当前节点均为空,则返回True;两棵树一个当前节点为空,一个不为空,则返回False。2、主要逻辑:如果两颗树的当前节点值相同,且node1.left、node2.right互为镜像,且node1.right、node2.left互为镜像,则返回为True。代码如下:

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        return self.helper(root,root)
    def helper(self, node1:TreeNode, node2:TreeNode)->bool:
        if not node1 and not node2:
            return True
        if not node1 or not node2:
            return False
        return node1.val==node2.val and self.helper(node1.left,node2.right) and self.helper(node1.right, node2.left)

102 二叉树层次遍历

这个题目没啥特别好说的,树的基本遍历方式之一,甚至不需要用到递归,直接两层for循环就解决了,代码如下:

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        res,cur_layer = [],[]
        if not root:
            return res
        cur_layer.append(root)
        while cur_layer:
            cur_temp,cur_val = [],[]
            for node in cur_layer:
                if node.left:
                    cur_temp.append(node.left)
                if node.right:
                    cur_temp.append(node.right)
                cur_val.append(node.val)
            res.append(cur_val)
            cur_layer = cur_temp
        return res

104 二叉树最大深度

这个题目属于简单题,直接三步走吧。1、递归结束条件:来到叶子节点,直接返回为0。2、主要代码逻辑,二叉树的最大深度为max(左子树最大深度、右子树最大深度)+1。代码如下:

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right))+1

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

这道题目可以先把什么递归啊遍历啊先抛开都不管,当自己什么思路都没有的时候,就先试试自己走一个例子,然后从特殊到一般具体看要用什么方法解题。树是这样,动态规划确定动态空间、base、与动态转移方程也是这样。ok,那先看看具体的题目:

前序遍历特点是先获取根节点的元素值,中序遍历是以左子树-根节点-右子树的方式去遍历。所以,由前序遍历的第一个节点,节后中序遍历,可以得出左子树的元素个数为1,根节点元素为3,右子树的元素个数为3,。于是根据preorder[-3:]与inorder[:-3]继续走重构的套路,所以很明显是一个递归的套路。于是考虑之前的三步走:1、递归终止条件,如果preorder的长度为0,则直接返回None;如果长度为1,则返回根节点元素为preorder[0]的TreeNode。2.主体逻辑,首先排除掉根节点元素,root.left = 左半部分元素继续重构。root.right = 右半部分元素继续重构。代码如下:

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if not preorder:
            return None
        if len(preorder) == 1:
            return TreeNode(preorder[0])
        root_index = inorder.index(preorder[0])
        root = TreeNode(preorder[0])
        root.left = self.buildTree(preorder[1 : root_index + 1], inorder[:root_index])
        root.right = self.buildTree(preorder[root_index + 1:], inorder[root_index + 1:])
        return root

114 二叉树展开为链表

这道题难度较大,先读题目:

如果不考虑题目中要求的原地两个字,那就直接gg了,害,不就是前序遍历再重构嘛,拿到前序遍历的val列表,然后list[0].right = list[1],list[1].right =list[2]..如此这般题目就解决了,当然这样好像也是原地的一种,就是显得有点low。其实在面对树的问题时,很多时候可以先将树通过前序、中序、后序或者层序遍历转换为数组(列表),再来进行求解,不管白猫黑猫抓到耗子就是好猫。ok,先试试这一种方法吧。需要注意的是,前序遍历得到的是val列表,不是TreeNode列表,如果是TreeNode列表的话需要先遍历把TreeNode的left 和right都置为None,再进行拼接。该思路代码如下:

class Solution:
    def flatten(self, root: TreeNode) -> None:
        preorder = self.preorder(root)
        n = len(preorder)
        if n <= 1:
            return
        root.left = None
        root.right = TreeNode(preorder[1])
        cur = root.right
        for i in range(2, n):
            cur.right = TreeNode(preorder[i])
            cur = cur.right

    def preorder(self, root:TreeNode):
        if not root:
            return []
        res = []
        res.append(root.val)
        res += self.preorder(root.left)
        res += self.preorder(root.right)
        return res

思路二:这道题,出题人的原本意图肯定不是希望我们用前序遍历再拼接去解题的,面试官看到这种答题方式只会说你个poor guy。所以考虑别的方法,其实简单分解一下步骤是这样的,1、先把左子树拉平,变成2.right = 3,3.right=4的这样的一棵子树,2、再把右子树拉平,题目给出的测试例中右子树已经是平的了。3、找到左子树的最右叶子节点,将右子树拼接到最右叶子结点之后。4、此时右子树为空,将左子树移到右子树即可,结束。其中每一个子树都是这样的处理过程,所以采用递归栈式调用。

代码如下:

class Solution:
    def flatten(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        if root is None:
            return
        self.flatten(root.left)
        self.flatten(root.right)
        if root.left:
            pre = root.left
            while pre.right :
                pre = pre.right
            pre.right = root.right
            root.right = root.left
            root.left = None

226 翻转二叉树

这题目和之前判断是否是对称二叉树一致。还是一样的,终止条件+主体对换代码+两路递归结束,没啥特别需要注意的点。

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root:
            return
        root.left,root.right = root.right, root.left
        self.invertTree(root.left)
        self.invertTree(root.right)
        return root

437 路径总和III

个人觉得这道题的难点在于路径的起点和终点都是不确定的,这道题直接能够想到的就是肯定是dfs来确定当前走过的列表的尾节点,然后遍历求值,如果相等则进行计数;另外应该涉及回溯,遍历左节点之后再遍历右节点。右节点的值应该会覆盖左节点原本的值。

class Solution:
    def __init__(self):
        self.sum = 0
    def pathSum(self, root: TreeNode, sum: int) -> int:
        self.sum = sum
        lyst = [0 for _ in range(1000)]
        return self.getSum(root, lyst, 0)

    def getSum(self, root: TreeNode, vals: [], layer):
        if not root:
            return 0
        vals[layer] = root.val
        cur, temp = 0, 0
        for i in range(layer, -1, -1):
            temp += vals[i]
            if temp == self.sum:
                cur += 1
        return cur + self.getSum(root.left, vals, layer + 1) + self.getSum(root.right, vals, layer + 1)

538 把二叉树转换为累加树

class Solution:
    def __init__(self):
        self.total = 0
    def convertBST(self, root: TreeNode) -> TreeNode:
        if not root:
            return
        self.convertBST(root.right)
        self.total += root.val
        root.val = self.total
        self.convertBST(root.left)
        return root

543 二叉树的直径

这道题目很明显是需要辅助函数的,如果要求直径必须经过根节点的话,那么只要对104题二叉树的最大深度稍加改动,求出L+R+1即可。但是这道题,L+R+1仅仅用于递归中间的比较过程,而不作为递归返回的结果,递归返回的是以当前节点为根节点的最大节点深度(但是仅仅是左子树或者右子树)。

class Solution:
    def diameterOfBinaryTree(self, root: TreeNode) -> int:
        res = 1

        def backtrack(node):
            nonlocal res
            if not node:
                return 0
            L = backtrack(node.left)
            R = backtrack(node.right)
            res = max(L + R + 1, res)
            return max(L, R) + 1

        backtrack(root)
        return res - 1

617 合并二叉树

把合并后的结果都放到t1上去,代码如下:

class Solution:
    def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode:
        if not t1:
            return t2
        if not t2:
            return t1
        t1.val += t2.val
        t1.left = self.mergeTrees(t1.left, t2.left)
        t1.right = self.mergeTrees(t1.right, t2.right)
        return t1

124 二叉树的最大路径和

与543题类似,只不过一个是加1一个是加节点的值,代码如下:

class Solution:
    def maxPathSum(self, root: TreeNode) -> int:
        def max_gain(node):
            nonlocal max_sum
            if not node:
                return 0
            left_gain = max(max_gain(node.left), 0)
            right_gain = max(max_gain(node.right), 0)

            price_newpath = node.val + left_gain + right_gain
            max_sum = max(max_sum, price_newpath)
            return node.val + max(left_gain, right_gain)

        max_sum = float("-inf")
        max_gain(root)
        return max_sum

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值