刷题记录-HOT 100(四)二叉树

1、二叉树的中序遍历

①递归

处理逻辑:递归地访问左子树-处理中间节点-递归地访问右子树。时间复杂度和空间复杂度都是O(n)。

# 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 inorder(self,root,ans):
        if not root:
            return
        self.inorder(root.left,ans)
        ans.append(root.val)
        self.inorder(root.right,ans)
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ans=[]
        self.inorder(root,ans)
        return ans

②迭代

递归是隐式地调用栈来遍历二叉树,迭代是显式地使用栈来保存节点的状态。时间复杂度和空间复杂度都是O(n)。

  • 栈的初始化:为了模拟递归调用,使用一个栈来保存树的节点。栈的主要作用是在深入左子树的过程中存储父节点,便于回退到根节点并转向右子树。
  • 循环遍历节点:通过一个主循环,按照中序遍历的方式遍历整个二叉树,首先遍历左子树,然后回到根节点,最后访问右子树。
  • 输出结果:每次从栈中弹出节点后,将节点的值添加到结果列表中。
# 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 inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ans=[];stk=[]
        while stk or root:
            while root:
                stk.append(root)
                root=root.left
            root=stk.pop()
            ans.append(root.val)
            root=root.right
        return ans

③Morris中序遍历

Morris 中序遍历通过修改树结构中的指针来避免使用递归或栈。当访问一个节点时,若该节点的左子树为空,则直接访问该节点;若左子树不为空,则找到左子树的最右节点,并将其右指针指向当前节点,这样就能在不使用额外空间的情况下回溯到该节点。时间复杂度O(n),空间复杂度O(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 inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ans=[]
        curr=root
        while curr:
            if not curr.left:
                ans.append(curr.val)
                curr=curr.right
            else:
                pre=curr.left
                while pre.right and pre.right!=curr:
                    pre=pre.right
                if not pre.right:
                    pre.right=curr
                    curr=curr.left
                else:
                    pre.right=None
                    ans.append(curr.val)
                    curr=curr.right
        return ans

2、二叉树的最大深度

①深度优先

递归遍历每个节点,计算其左右子树的深度,并返回左右子树中较大的深度加上 1,即为当前节点的最大深度。如果当前节点为空,说明树的深度为 0。

  • 时间复杂度:O(n)

  • 空间复杂度:O(height)

# 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 not root:
            return 0
        else:
            left=self.maxDepth(root.left)
            right=self.maxDepth(root.right)
            return max(left,right)+1

②广度优先

广度优先队列中存储的是当前层全部结点,每次要扩展到下一层的时候,需要出队当前层的全部结点进行扩展。扩展的次数用ans记录,也就是二叉树的最大深度。

# 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 not root:
            return 0
        q=collections.deque()
        q.append(root)
        ans=0
        while q:
            size=len(q)
            while size>0:
                node=q.popleft()
                if node.left:
                    q.append(node.left)
                if node.right:
                    q.append(node.right)
                size-=1
            ans+=1
        return ans

3、翻转二叉树

也是用递归的思想进行处理,使用递归的方法进行深度优先遍历,依次交换每个节点的左右子树。递归的终止条件是当前节点为空。

# 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 invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if not root:
            return None
        self.invertTree(root.left)
        self.invertTree(root.right)
        root.left,root.right=root.right,root.left
        return root

4、对称二叉树

①递归

从根节点开始,递归地比较左子树的左子节点与右子树的右子节点,左子树的右子节点与右子树的左子节点,只有当所有对应节点相等时,树才是对称的。

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
# 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 isMirror(self,left,right):
        if not left and not right:
            return True
        if not left or not right or left.val!=right.val:
            return False
        return self.isMirror(left.left,right.right) and self.isMirror(left.right,right.left)
    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True
        return self.isMirror(root.left,root.right)

②迭代

使用一个队列来实现,队列中存储的是成对的节点(两个一组),每次出队两个节点并比较它们的值。如果值不同或者结构不匹配,则不对称;否则,继续将它们的左右子节点成对加入队列,直到队列为空。

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
# 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 isSymmetric(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True 
        q=collections.deque()
        q.append((root.left,root.right))
        while q:
            left,right=q.popleft()
            if not left and not right:
                continue
            if not left or not right or left.val!=right.val:
                return False
            q.append((left.left,right.right))
            q.append((left.right,right.left))
        return True

5、二叉树的直径

利用DFS来计算每个节点的左右子树的深度,然后通过递归判断每个节点的左右子树深度之和是否为最大值,并最终得到最大直径。

需要注意的是,depth求出来的深度并不是边的长度,而是节点个数。因此最后通过计算左右子树深度获得的直径需要减一。

# 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 depth(self,node):
        if not node:
            return 0
        left_depth=self.depth(node.left)
        right_depth=self.depth(node.right)
        self.diameter=max(self.diameter,left_depth+right_depth+1)
        return max(left_depth,right_depth)+1
    def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
        self.diameter=0
        self.depth(root)
        return self.diameter-1

6、二叉树的层序遍历

注意输出形式,其中位于一层的节点放在一个列表中。

还是利用队列进行层序遍历,先入队root。

在队列非空的情况下(队列中此时装着一层的结点),首先得到该层结点的个数,再逐个将结点出队,把出队结点的值保存到临时列表tmp中,探查是否存在左右节点进行入队。

# 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 levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []
        q=collections.deque();ans=[]
        q.append(root)
        while q:
            size=len(q)
            tmp=[]
            while size:
                p=q.popleft()
                tmp.append(p.val)
                if p.left:
                     q.append(p.left)
                if p.right:
                     q.append(p.right)
                size-=1
            ans.append(tmp)
        return ans

7、将有序数组转换为二叉搜索树

可以通过递归将数组中的中间元素作为根节点,左右子数组分别构造该根节点的左右子树,从而保证树的平衡性。

每次选取中间靠右的元素(left+right+1)//2作为根结点构建子树。

  • 递归基准条件:如果 left > right,说明子数组已经遍历完毕,返回 None,表示子树为空。
  • 选择中间元素作为根节点:为了保持树的平衡,每次递归选择子数组的中间元素作为当前子树的根节点。
  • 递归构建左右子树:递归地对中间元素左侧的子数组构建左子树,右侧的子数组构建右子树。
  • 返回根节点:递归的每一层返回当前子树的根节点,最终形成一棵平衡的二叉搜索树。
# 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 arraySort(self,left,right,nums):
        if left>right:
            return None
        mid=(left+right+1)//2
        root=TreeNode(nums[mid])
        root.left=self.arraySort(left,mid-1,nums)
        root.right=self.arraySort(mid+1,right,nums)
        return root
    def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
        return self.arraySort(0,len(nums)-1,nums)

8、验证二叉搜索树

①递归

  • 递归基准条件:如果当前节点为空(None),说明达到了叶子节点的下一个层次,返回 True,表示该分支是合法的 BST。
  • 值的约束:对于每个节点,我们传入一个有效的值区间,即当前节点的值必须大于最小值 min_val,并且小于最大值 max_val。如果当前节点不符合这个条件,返回 False
  • 递归验证左右子树:递归验证左子树时,更新右边界 max_val为当前结点的val,即左子树的所有节点值必须小于当前节点值;递归验证右子树时,更新左边界 min_val为当前结点的val,即右子树的所有节点值必须大于当前节点值。
# 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 helper(self,root,min_val,max_val):
        if not root:
            return True
        val=root.val
        if not (min_val<root.val<max_val):
            return False
        left=self.helper(root.left,min_val,val)
        right=self.helper(root.right,val,max_val)
        return left and right
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        return self.helper(root,float('-inf'),float('inf'))

②利用中序遍历验证

中序遍历的结果应该是一个严格递增的序列,因此通过比较每个节点的值与前一个节点的值,可以判断这棵树是否满足 BST 的性质。

# 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 __init__(self):
        self.pre=float('-inf')
    def helper(self,root):
        if not root:
            return True
        if not self.helper(root.left):
            return False
        if root.val<=self.pre:
            return False
        self.pre=root.val
        return self.helper(root.right)
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        return self.helper(root)

9、二叉搜索树中第K小的元素

使用中序遍历来查找二叉搜索树中的第 k 小元素。二叉搜索树的中序遍历能够按顺序访问树中的元素,按照升序排列。因此,遍历到第 k 个节点时,即得到了第 k 小的元素。

# 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 __init__(self):
        self.k=0
        self.res=0
    def inorder(self,root):
        if not root:
            return
        self.inorder(root.left)
        self.k-=1
        if self.k==0:
            self.res=root.val
            return
        self.inorder(root.right)
    def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
        self.k=k
        self.inorder(root)
        return self.res

10、二叉树的右视图

①深度优先搜索

按照根结点-右子树-左子树的访问顺序,可以保证每一层第一个被访问到的节点一定是该层最右边的结点。

最右结点被存储在一个全局的结果列表中。

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
# 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 __init__(self):
        self.res=[]
    def dfs(self,root,depth):
        if not root:
            return
        if depth==len(self.res):
            self.res.append(root.val)
        self.dfs(root.right,depth+1)
        self.dfs(root.left,depth+1)
    def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
        self.dfs(root,0)
        return self.res

②广度优先搜索

层次遍历二叉树,每层的最后一个结点就是二叉树在该层的最右结点。最右结点同样被存储在一个全局的结果列表中。

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
# 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 __init__(self):
        self.res=[]
    def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        q=collections.deque()
        q.append(root)
        while q:
            size=len(q)
            while size:
                p=q.popleft()
                if p.left:
                    q.append(p.left)
                if p.right:
                    q.append(p.right)
                size-=1
                if size==0:
                    self.res.append(p.val)
        return self.res

11、二叉树展开为链表

题目的要求是原地展开为链表,因此最终的结构应该是每个节点的左子树为空,所有节点都沿着右子树形成链表。

采用的是后序遍历(先右再左再根结点),原因是我们需要在修改当前节点的右指针时,已经处理好了它的右子树。通过递归方式,保证先处理右子树和左子树,这样在递归回溯时,我们已经完成了每个节点的右子树链接,可以直接修改当前节点的指针。

通过一个全局指针prev来指向上一个处理好的结点,当前处理的结点的右指针指向上一个处理好的结点,这样就连接成了链表。

拿下面的二叉树举例:

  • 先处理根结点1的右子树5-6。5-6的根结点为5,右子树为6,先处理6,6的next指向prev,prev更新指向6。由于5没有左子树,因此不用处理左子树就直接处理根结点5,5的next指向prev(也就是6),prev更新指向5。此时,处理完的部分是5-6。
  • 处理完根结点1的右子树5-6后,接着处理1的左子树3-2-4。对于3-2-4来说,先处理右子树4,4的next指向prev(也就是5),prev更新指向4;再处理左子树3,3的next指向prev(也就是4),prev更新指向3;最后处理子树根结点2,2的next指向prev(也就是3),prev更新指向2。此时处理完的部分是2-3-4-5-6。
  • 最后处理根结点1,1的next指向prev(也就是2),prev更新指向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 __init__(self):
        self.prev=None
    def flatten(self, root: Optional[TreeNode]) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        if not root:
            return 
        self.flatten(root.right)
        self.flatten(root.left)
        root.right=self.prev
        root.left=None
        self.prev=root

12、从前序和中序序列构造二叉树

①递归

使用递归的方式,按照以下步骤构建二叉树:

  1. 前序遍历的第一个元素是根节点。
  2. 在中序遍历中找到根节点的位置,该位置将中序序列分为左子树部分和右子树部分。
  3. 递归处理前序和中序序列中的左子树和右子树。

 时间复杂度:O(n),空间复杂度:O(n)。

# 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, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if not preorder or not inorder:
            return None
        root_val=preorder[0]
        root=TreeNode(root_val)
        root_index_inorder=inorder.index(root_val)

        left_inorder=inorder[:root_index_inorder]
        right_inorder=inorder[root_index_inorder+1:]

        left_preorder=preorder[1:root_index_inorder+1]
        right_preorder=preorder[root_index_inorder+1:]

        root.left=self.buildTree(left_preorder,left_inorder)
        root.right=self.buildTree(right_preorder,right_inorder)
        return root


②迭代

  • 前序遍历告诉我们每个节点的根节点顺序,从根到左子树再到右子树。
  • 中序遍历告诉我们每个节点的左子树和右子树的划分边界。
  • 使用一个栈来记录已经构建好的节点:
    • 如果当前节点与栈顶节点在中序遍历中的顺序不符,那么当前节点是栈顶节点的左子节点。
    • 如果当前节点与栈顶节点在中序遍历中的顺序一致,则说明栈顶节点的左子树已经处理完了,需要为栈顶节点找到右子节点。

 时间复杂度:O(n),空间复杂度:O(n)。

# 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, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        if not preorder:
            return None
        
        root=TreeNode(preorder[0])
        stk=[root]
        inorder_index=0

        for i in range(1,len(preorder)):
            node=TreeNode(preorder[i])
            parent=stk[-1]

            if parent.val!=inorder[inorder_index]:
                parent.left=node
                stk.append(node)
            else:
                while stk and stk[-1].val==inorder[inorder_index]:
                    parent=stk.pop()
                    inorder_index+=1
                parent.right=node
                stk.append(node)
        return root

13、路径总和

①深度优先搜索

  • 计算从某个节点出发的路径数

    • 使用 rootSum 函数递归计算从当前节点出发,向下的所有路径中,路径和等于 targetSum 的路径数。
  • 遍历整棵二叉树

    • 使用 pathSum 函数遍历整棵树,确保每个节点都被作为起点调用 rootSum,计算其下所有路径的和。
  • 递归检查左右子树

    • 对每个节点,我们还需要递归地遍历其左右子树,保证所有节点都被检查到。
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(h)
# 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 rootSum(self,root,targetSum):
        if not root:
            return 0
        ret=0
        if root.val==targetSum:
            ret+=1
        ret+=self.rootSum(root.left,targetSum-root.val)
        ret+=self.rootSum(root.right,targetSum-root.val)
        return ret
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
        if not root:
            return 0
        ret=self.rootSum(root,targetSum)
        ret+=self.pathSum(root.left,targetSum)
        ret+=self.pathSum(root.right,targetSum)
        return ret

②前缀和

  • 前缀和的使用

    • 前缀和表示从根节点到当前节点路径上所有节点的和。
    • 如果我们知道某个节点的前缀和 s,并且知道在路径中某一位置的前缀和是 s - targetSum,那么从这个位置到当前节点的路径和就是 targetSum
  • 哈希表的使用:

    • 哈希表 cnt 用来记录每个前缀和出现的次数,以帮助快速查找某个前缀和是否存在。
    • 通过查找前缀和 s - targetSum,可以判断从该前缀和位置到当前节点的路径和是否等于 targetSum
  • 递归 DFS 遍历

    • 使用深度优先搜索(DFS)遍历二叉树的每个节点,计算当前节点的前缀和。
    • 每次递归时更新前缀和,并通过查找 cnt[s - targetSum] 判断是否存在满足条件的路径。
    • 遍历子树之后恢复前缀和计数,回溯到父节点,避免影响其他路径的计算。
class Solution:
    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
        ans = 0
        cnt = defaultdict(int)
        cnt[0] = 1

        def dfs(node: Optional[TreeNode], s: int) -> None:
            if node is None:
                return
            nonlocal ans
            s += node.val
            ans += cnt[s - targetSum]
            cnt[s] += 1
            dfs(node.left, s)
            dfs(node.right, s)
            cnt[s] -= 1  # 恢复现场

        dfs(root, 0)
        return ans

14、二叉树的最近公共祖先

最近公共祖先指的是在树中位于节点 pq 之上的一个节点,并且该节点的子树同时包含 pq。可以通过递归的方式从根节点开始遍历树。

  • 如果当前节点是 None,或者当前节点等于 pq,那么返回当前节点。
  • 递归检查左子树和右子树,分别找出左、右子树中是否包含 pq
  • 如果左子树返回非空,且右子树也返回非空,则当前节点就是最近的公共祖先。
  • 如果只有一侧的子树返回非空,那么说明公共祖先在该子树中。
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if not root or root==p or root==q:
            return root
        left=self.lowestCommonAncestor(root.left,p,q)
        right=self.lowestCommonAncestor(root.right,p,q)
        if not left:
            return right
        if not right:
            return left
        return root

15、二叉树中的最大路径和

  • 通过递归遍历整棵树,计算每个节点的最大贡献值,即从该节点到其某个子树的最大路径和(也就是该节点的值加上它左子树的最大贡献值和右子树的最大贡献值)。当一个节点的贡献值小于 0 时,路径中不应包含该子树,因为它只会让路径和变小。所以在计算时,我们选择左右子树中非负的贡献值。
  • 对于每个节点,路径可以通过左子树到该节点,再通过右子树,这形成了以该节点为根的路径和。
  • 同时,在递归过程中,维护全局变量 self.maxSum 来记录当前发现的最大路径和。
# 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 __init__(self):
        self.maxsum=float('-inf')
    def maxGain(self,node):
        if not node:
            return 0
        left=max(self.maxGain(node.left),0)
        right=max(self.maxGain(node.right),0)
        newpath=node.val+left+right
        self.maxsum=max(self.maxsum,newpath)
        return node.val+max(left,right)
    def maxPathSum(self, root: Optional[TreeNode]) -> int:
        self.maxGain(root)
        return self.maxsum

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值