Day6,Hot100(二叉树)

二叉树


(1)104. 二叉树的最大深度

方法1:二叉树的最大深度 = max(左子树的深度,右子树的深度) + 1

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if root is None:
            return 0
        
        max_l = self.maxDepth(root.left)
        max_r = self.maxDepth(root.right)

        return max(max_l, max_r) + 1 

方法2:递归遍历树,每走一步深度+1

class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        ans = 0
        
        def f(node, cnt):
            if node is None:
                return 
            cnt += 1
            
            nonlocal ans
            ans = max(ans, cnt)

            f(node.left, cnt)
            f(node.right, cnt)

        f(root, 0)

        return ans

(2)100.相同的树

判断两棵树是否相同?

  • 首先递归判断左子树是否相同
  • 然后再递归判断右子树是否相同
class Solution:
    def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        # 只有p,q同时为none时, 才说明两棵树是相同的
        if p is None or q is None:
            return p == q  
        
        if p.val != q.val:
            return False
        
        return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)

(3)101.对称二叉树

结合<相同的树>的代码,判断左右子树是否相同:

  • 左子树的左子树 == 右子树的右子树
  • 左子树的右子树 == 右子树的左子树
class Solution:
    def isSameTree(self, p, q):
        if p is None or q is None:
            return p == q
        
        if p.val != q.val:
            return False
        
        return self.isSameTree(p.left, q.right) and self.isSameTree(p.right, q.left)

    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
        return self.isSameTree(root.left, root.right)

(4)110.平衡二叉树

平衡二叉树 --> 左右子树的高度差不超过1

递归计算左右子树的高度, 最终判断左右子数的高度差

class Solution:
    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        def get_height(node):
            if node is None:
                return 0
            
            left_height = get_height(node.left)
            if left_height == -1:
                return -1

            right_height = get_height(node.right)
            if right_height == -1 or abs(left_height-right_height) > 1:
                return -1
            
            return max(left_height, right_height) + 1
        
        return get_height(root) != -1

(5)199.二叉树的右视图

只有当 深度 == 当前答案的长度 时,答案才会被记录

class Solution:
    def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
        ans = []

        def f(node, depth):
            if node is None:
                return
            if len(ans) == depth:
                ans.append(node.val)

            f(node.right, depth+1)
            f(node.left, depth+1)
        
        f(root, 0)

        return ans

(6)226. 翻转二叉树

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if root is None:
            return None
        
        left = self.invertTree(root.left)
        right = self.invertTree(root.right)

        root.left = right
        root.right = left

        return root

(7)108. 将有序数组转换为二叉搜索树

class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
        if not nums:
            return None
        mid = len(nums) // 2

        left = self.sortedArrayToBST(nums[:mid])
        right = self.sortedArrayToBST(nums[mid+1:])

        return TreeNode(nums[mid], left, right)


二叉树的遍历

(1)94. 二叉树的中序遍历

二叉树的前中后序遍历(前中后表示的是根节点的遍历顺序)

  • 前序遍历(Pre-order Traversal):根,左,右
  • 中序遍历(In-order Traversal):左,根,右
  • 后序遍历(Post-order Traversal):左,右,根

前序遍历:ABDECFG
中序遍历:DBEAFCG
后序遍历:DEBFGCA

方法一:递归写法

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ans = []

        def f(node):
            if node is None:
                return 
            f(node.left)
            ans.append(node.val)
            f(node.right)
        
        f(root)

        return ans

方法2:迭代

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        white, gray = 0, 1  # white表示未被访问, gray表示被访问
        ans = []
        stack = [(white, root)]

        while stack:
            color, node = stack.pop()

            if node is None:
                continue

            if color == white:
                # 因为要保证出栈的时候是 左-->根-->右
                # 所以入栈的时候要是 右-->根-->左
                stack.append((white, node.right))
                stack.append((gray, node))
                stack.append((white, node.left))
            else:
                ans.append(node.val)
        
        return ans

(2)98. 验证二叉搜索树

方法1:前序遍历

  • root.left 的值域应该 <root.val,更新区间 (left, root.val)
  • root.right 的值域应该 >root.val,更新区间 (root.val, right)
class Solution:
    def check(self, node, left, right):
        if node is None:
            return True
        x = node.val
        return left < x < right and self.check(node.left, left, x) and self.check(node.right, x, right)

    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        return self.check(root, -inf, inf)

方法2:中序遍历
按照中序遍历的顺序,一个二叉搜索树,应该满足下一个结点比上一个结点大

class Solution:
    pre = -inf 

    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        if root is None:
            return True
        
        l = self.isValidBST(root.left)

        if root.val <= self.pre:
            return False
        self.pre = root.val

        r = self.isValidBST(root.right)

        return l and r

方法3:后序遍历

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        def f(node):
            if node is None:
                return inf, -inf
            l_min, l_max = f(node.left)
            r_min, r_max = f(node.right)
            x = node.val
            if x <= l_max or x >= r_min:
                return -inf, inf
            return min(l_min, x), max(r_max, x)
        
        return f(root)[0] != -inf

(3)230. 二叉搜索树中第 K 小的元素

中序遍历二叉搜索树,得到的是从小到大排序的序列,取第k个元素即可

class Solution:
    def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
        ans = []
        def f(node):
            if node is None:
                return None
            f(node.left)
            ans.append(node.val)
            f(node.right)
        f(root)
        return ans[k-1]

(4)114.展开二叉树为链表

在这里插入图片描述
写法一

class Solution:
    def flatten(self, root: Optional[TreeNode]) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        ls = []

        def dfs(node):
            if not node:
                return
            
            ls.append(node)
            dfs(node.left)
            dfs(node.right)
    
        dfs(root)

        n = len(ls)
        if n > 0:
            head = ls[0]
            for node in ls[1:]:
                head.left = None
                head.right = node
                head = head.right

写法二

class Solution:
    def flatten(self, root: Optional[TreeNode]) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        ans = []
        def dfs(node):
            if node is None:
                return 
            ans.append(node)
            dfs(node.left)
            dfs(node.right)
        dfs(root)

        n = len(ans)
        for i in range(1, n):
            pre, cur = ans[i-1], ans[i]
            pre.left = None
            pre.right = cur 

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

在这里插入图片描述
在这里插入图片描述

  • 前序找根(根,左,右)
  • 中序来分(左,根,右)
  • 根据前序找到根节点,在根据中序,找到根节点的左右子结点
    (同理,后序+中序也是一样的思路,在后序找根,中序来分)

步骤

  1. 前序数组为空时,空结点(相当于没有根节点了)
  2. 前序数组的第一个元素为 根节点
  3. 找到根节点后,在中序数组中切分,找到根节点的左右子树
  4. 切分中序数组,得到左右区间
  5. 切分前序数组,得到左右区间
  6. 递归处理左右区间
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        # 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件
        if not preorder:
            return None

        # 第二步: 前序遍历的第一个就是当前的中间节点.
        root_val = preorder[0]
        root = TreeNode(root_val)

        # 第三步: 找切割点.
        separator_idx = inorder.index(root_val)

        # 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
        inorder_left = inorder[:separator_idx]
        inorder_right = inorder[separator_idx + 1:]

        # 第五步: 切割preorder数组. 得到preorder数组的左,右半边.
        preorder_left = preorder[1:1 + len(inorder_left)]
        preorder_right = preorder[1 + len(inorder_left):]

        # 第六步: 递归
        root.left = self.buildTree(preorder_left, inorder_left)
        root.right = self.buildTree(preorder_right, inorder_right)
        
        # 第七步: 返回答案
        return root

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

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
        # 为空返回
        if not postorder:
            return 
        
        # 1.找根
        root_val = postorder[-1]
        root = TreeNode(val=root_val)

        # 2.中序划分左右区间
        inorder_idx = inorder.index(root_val)
        inorder_left = inorder[:inorder_idx]
        inorder_right = inorder[inorder_idx+1:]

        # 3.后续划分左右
        postorder_left = postorder[:len(inorder_left)]
        postorder_right = postorder[len(inorder_left):-1]

        root.left = self.buildTree(inorder_left, postorder_left)
        root.right = self.buildTree(inorder_right, postorder_right)

        return root


二叉树的最近公共祖先

结点的祖先(根节点)

  • 对于结点6,它的祖先有6,5,3(包括自己本身)
  • 对于结点4,它的祖先有4,2,5,3

结点的公共祖先

  • 结点6和4的公共祖先,5,3
  • 最近公共祖先5

(1)236. 二叉树的最近公共祖先

(1)找到 p 或 q 后,无需继续遍历后续结点——>返回当前结点

例如找到 p = 5,无论 q5 后续的哪一个结点,pq的最近公共祖先都是5

(2)如果在左右子树分别找到了pq ——>返回当前结点

例如找到 p = 5q = 1,则最近公共祖先就是当前结点3

(3)如果只在其中一个子树中找到了pq ——>返回左(或右)子树找到的结果

例如,在左子树中找到了p=5q=4

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if root is None or root is p or root is q:
            return root

        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)

        if left and right:
            return root
        if left:
            return left
        if right:
            return right

(2)235. 二叉搜索树的最近公共祖先

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        x = root.val
        if p.val < x and q.val < x:  # 都在左子树
            return self.lowestCommonAncestor(root.left, p, q)
        if p.val > x and q.val > x:  # 都在右子树
            return self.lowestCommonAncestor(root.right, p, q)
        return root

二叉树的层次遍历

(1)102. 二叉树的层序遍历

  • 用队列
  • 队列初始时要加括号,q = deque([root]),可迭代的 list 才能被初始化为 deque
  • 出队时记录值,并将其 左右 结点添加到队列中
from collections import deque
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if root is None:
            return []
        
        ans = []
        q = deque([root])

        while q:
            vals = []
            for _ in range(len(q)):
                node = q.popleft()
                vals.append(node.val)
                if node.left:
                    q.append(node.left)
                if node.right:
                    q.append(node.right)
            ans.append(vals)
            
        return ans

不用队列

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if root is None:
            return []
        
        ans = []
        cur = [root]

        while cur:
            nxt = []
            vals = []
            for node in cur:
                vals.append(node.val)
                if node.left:
                    nxt.append(node.left)
                if node.right:
                    nxt.append(node.right)
            ans.append(vals)
            cur = nxt
            
        return ans

(2)103. 二叉树的锯齿形层序遍历

第一层,从左往右
第二次,从右往左
……

from collections import deque
class Solution:
    def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if root is None:
            return []
        
        ans = []
        q = deque([root])
        layer = 1

        while q:
            vals = []
            for _ in range(len(q)):
                node = q.popleft()
                vals.append(node.val)
                if node.left:
                    q.append(node.left)
                if node.right:
                    q.append(node.right)
            if layer % 2 > 0:
                ans.append(vals)
            else:
                ans.append(vals[::-1])
            layer += 1
        return ans

(3)513. 找树左下角的值

求最后一层最左边的结点

方法一:
层次遍历,并保存每一层的第一个结点,最终得到最后一层的第一个结点

from collections import deque
class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        q = deque([root])
        ans = None

        while q:
            for i in range(len(q)):
                node = q.popleft()
                if node.left:
                    q.append(node.left)
                if node.right:
                    q.append(node.right)
                if i == 0:
                    ans = node.val
        return ans

方法二:
修改层次遍历的顺序,修改为从右往左,这样子最左边的结点就是队列中的最后一个结点

from collections import deque
class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        q = deque([root])
        ans = None

        while q:
            node = q.popleft()
            if node.right:
                q.append(node.right)
            if node.left:
                q.append(node.left)
        return node.val

路径总和

(1)112. 路径总和

在这里插入图片描述

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

        def dfs(node, count):  # count 表示剩余的和
            if not node.left and not node.right:  # 当前是叶子结点
                if count == 0:  # 当前路径满足条件
                    return True
                else:
                    return False
            
            if node.left:
                count -= node.left.val
                if dfs(node.left, count):
                    return True
                count += node.left.val
            
            if node.right:
                count -= node.right.val
                if dfs(node.right, count):
                    return True
                count += node.right.val
            
            return False

        return dfs(root, targetSum - root.val)      

(2)113. 路径总和 II

在这里插入图片描述
注意初始时要,path.append(root.val)

class Solution:
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        if not root:
            return []
        
        ans = []
        path = []

        def dfs(node, count): 
            if not node.left and not node.right:
                if count == 0:
                    ans.append(path.copy())
                return 

            if node.left:
                count -= node.left.val
                path.append(node.left.val)
                dfs(node.left, count)
                count += node.left.val
                path.pop()

            if node.right:
                count -= node.right.val
                path.append(node.right.val)
                dfs(node.right, count)
                count += node.right.val
                path.pop()

        path.append(root.val)
        dfs(root, targetSum-root.val)
        return ans

(3)437. 路径总和 III

在这里插入图片描述
在这里插入图片描述
类似<560. 和为 K 的子数组>的解法:

  • 二叉树的前缀和 = 根节点到当前结点的路径和
from collections import defaultdict
class Solution:
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
        ans = 0
        cnt = defaultdict(int)
        cnt[0] = 1

        # s 从根节点到当前结点的总和
        def dfs(node: Optional[TreeNode], s: int) -> None:
            if node is None:
                return

            nonlocal ans
            s += node.val
            tar_pre = s - targetSum
            if tar_pre in cnt:
                ans += cnt[tar_pre]
            cnt[s] += 1

            dfs(node.left, s)
            dfs(node.right, s)
            cnt[s] -= 1  # 恢复现场

        dfs(root, 0)
        return ans

树形DP

(1)543. 二叉树的直径

在这里插入图片描述
最长直径的起点终点一定都是叶子结点

假设直径会经过某个点(例如上图中的 node 1)

  • 那么对于当前结点,对应的最大直径为:左子树的最大深度+右子树最大深度+2,( 2 表示左右子树到 node 1 的路径长度)
  • 深度 == max(左子树, 右子树) + 1,所以其实相当于是在求最大深度的过程中,更新最大直径

注意,if node is None: return -1

class Solution:
    def diameterOfBinaryTree(self, root: TreeNode) -> int:
        ans = 0 

        def dfs(node):
            if node is None:
                return -1
            left = dfs(node.left)
            right = dfs(node.right)
            nonlocal ans
            ans = max(ans, left + right + 2)
            return max(left, right) + 1
        
        dfs(root)

        return ans

(2)124. 二叉树中的最大路径和

在这里插入图片描述

在这里插入图片描述

同样对于每个结点,计算它左右子树的最大链和

  • 当前拐点的最大链和 = 左 + 右 + 当前拐点值
  • 注意:当左或右为负数时,可以返回0,表示不经过该链
    在这里插入图片描述

所以其实是求最大带权值的过程中,更新答案

class Solution:
    def maxPathSum(self, root: Optional[TreeNode]) -> int:
        ans = -inf

        def dfs(node):
            if node is None:
                return 0
            left = max(0, dfs(node.left))
            right = max(0, dfs(node.right))
            nonlocal ans
            ans = max(ans, left + right + node.val)
            return max(left, right) + node.val
        
        dfs(root)
        return ans
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值