【科学刷题】完全吃透所有树相关的算法题

文章目录

1 二叉树

1.1 二叉树递归 / 层序遍历

1.1.1 二叉树的 对称 / 翻转 / 镜像

3个入门级简单题,汇总一下:

101. 对称二叉树

class Solution:
    def isSymmetric(self, root):
        # 56ms 击败10%
        # return self.check(root, root)
        if root is None:
            return True
        # 52ms 击败20%
        return self.check(root.left, root.right)

    def check(self, p, q):
        if p is None and q is None:
            return True
        if p is None or q is None:
            return False
        return p.val == q.val and self.check(p.left, q.right) and self.check(p.right, q.left)

226. 翻转二叉树

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

剑指 Offer 27. 二叉树的镜像

class Solution:
    def mirrorTree(self, root: TreeNode) -> TreeNode:
        def recur(node):
            if node is None:
                return None
            node.left, node.right = recur(node.right), recur(node.left)
            return node
        return recur(root)

1.1.2 二叉树的直径 / 最大路径和

1.1.2.1 二叉树的直径

这种类型的题有两个关键步骤:

  • 二叉树的直径
    • 更新:经过当前结点的最大长度
    • 返回:从当前结点出发的最大长度
  • 二叉树中的最大路径和
    • 更新:经过当前结点的最大路径和
    • 返回:从当前结点出发的最大路径

543. 二叉树的直径

有空重新刷一下,注意边界条件

class Solution:
    def diameterOfBinaryTree(self, root: TreeNode) -> int:
        ans = 1
        def rec(node:TreeNode):
            nonlocal ans
            if node is None:
                return 0
            rl, rr = rec(node.left), rec(node.right)
            # 更新:经过当前结点的最大长度
            ans = max(ans, 1 + rl + rr)
            # 返回:从当前结点出发的最大长度
            return max(rl, rr) + 1
        
        rec(root)
        return ans - 1
1.1.2.2 二叉树中的最大路径和

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

class Solution:
    def maxPathSum(self, root: TreeNode) -> int:
        ans = -inf
        def recursion(node)->int:
            nonlocal ans
            if node is None:
                return 0
            # 如果为负数,做一个截断
            left = max(0, recursion(node.left))
            right = max(0, recursion(node.right))
            # 更新:经过当前结点的最大路径和
            ans = max(ans, left + right + node.val)
            # 返回:从当前结点出发的最大路径
            return node.val + max(left, right)
        recursion(root)
        return ans

1.1.3 二叉树的最大 / 最小深度

最大深度

104. 二叉树的最大深度

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

最小深度

111. 二叉树的最小深度

对于【二叉树的最小深度】,虽然BFS和DFS的时间复杂度相同,但一般来说BFS会更快,因为一般情况下只需要遍历部分结点即可返回

  • DFS
class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        if root.left is None and root.right is None:
            return 1
        ans = inf
        if root.left:
            ans = min(ans, self.minDepth(root.left))
        if root.right:
            ans = min(ans, self.minDepth(root.right))
        return ans + 1
  • BFS
class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root: return 0
        q = [(root, 1)]
        while q:
            top, d = q.pop(0)
            if top.left is None and top.right is None:
                return d
            if top.left:
                q.append((top.left, d + 1))
            if top.right:
                q.append((top.right, d + 1))

1.1.4 填充每个节点的下一个右侧节点指针

116. 填充每个节点的下一个右侧节点指针

  • 递归

题目要求常数空间。

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if root is None:
            return None
        def recursion(node1, node2):
            if node1 is None or node2 is None:
                return
            node1.next = node2
            recursion(node1.left, node1.right)
            recursion(node2.left, node2.right)
            recursion(node1.right, node2.left)
        recursion(root.left, root.right)
        return root
  • 层序遍历

跑起来快多了。。

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if root is None:
            return None
        queue = collections.deque()
        queue.append(root)
        while queue:
            sz = len(queue)
            pre = None
            for _ in range(sz):
                top = queue.popleft()
                if pre:
                    pre.next = top
                pre = top
                if top.left:
                    queue.append(top.left)
                if top.right:
                    queue.append(top.right)
        return root

117. 填充每个节点的下一个右侧节点指针 II

这题与上题不同,只能用BFS层序遍历求解了

1.1.5 二叉树的层序遍历

1.1.5.1 二叉树最大宽度

662. 二叉树最大宽度

class Solution:
    def widthOfBinaryTree(self, root: TreeNode) -> int:
        q = [(root, 0)]
        ans = 0
        max_width = 0
        while q:
            sz = len(q)
            lb, rb = inf, -inf
            for i in range(sz):
                top, idx = q.pop(0)
                lb = min(lb, idx)
                rb = max(rb, idx)
                max_width = max(max_width, (rb - lb + 1))
                if top.left:
                    q.append((top.left, idx * 2))
                if top.right:
                    q.append((top.right, idx * 2 + 1))

        return max_width

1.2 完全二叉树

1.2.1 复杂度 log ⁡ 2 N \log^2N log2N的题目

这类题目有一个共同的特点:

  1. 每次遍历都需要计算一遍二叉树左右子树的高度(复杂度 log ⁡ N \log N logN
  2. 然后结合这个高度信息,再去遍历左右子树(复杂度 log ⁡ N \log N logN

二者叠加后,时间复杂度就变成了 log ⁡ 2 N \log^2N log2N

1.2.1.1 完全二叉树的结点个数

222. 完全二叉树的节点个数

算法笔记:如何计算完全二叉树的节点数

利用完全二叉树的性质:

  • 如果height_left == height_right
    • 是满二叉树,用公式计算结点数
  • 否则
    • 用递归计算结点数

时间复杂度 O ( l o g 2 N ) O(log^2N) O(log2N)

class Solution:
    def countNodes(self, root: TreeNode) -> int:
        r = l = root
        hr = hl = 0
        while r is not None:
            hr += 1
            r = r.right
        while l is not None:
            hl += 1
            l = l.left
        # 空指针也会在这里返回
        if hl == hr:
            return 2 ** (hl) - 1
        return 1 + self.countNodes(root.left) + self.countNodes(root.right)
1.2.1.2 完全二叉树的最后一个节点

字节跳动面试题.完全二叉树的最后一个节点

实习|算法岗血泪面经:商汤,旷世,阿里,字节跳动

这题的lhrh与上题的区别:
lh:从左节点出发,一直往左走
rh:从右结点出发,一直往

用这张图简单说明一下思路:

  • 如果左子树高度>右子树高度,目标结点必然在左子树
  • 如果左子树高度<=右子树高度,目标结点必然在右子树

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def getLastNode(root: TreeNode) -> TreeNode:
    if root is None or root.left is None:
        return root
    lp, rp = root.left, root.right
    lh = rh = 0
    while lp:
        lp = lp.left
        lh += 1
    while rp:
        rp = rp.left
        rh += 1
    if lh > rh:
        return getLastNode(root.left)
    elif lh <= rh:
        return getLastNode(root.right)

if __name__ == '__main__':
    case1 = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)),
                     TreeNode(3, TreeNode(6)))
    assert getLastNode(case1).val == 6
    case2 = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)),
                     TreeNode(3, TreeNode(6), TreeNode(7)))
    assert getLastNode(case2).val == 7
    case3 = TreeNode(1, TreeNode(2, TreeNode(4), TreeNode(5)),
                     TreeNode(3))
    assert getLastNode(case3).val == 5
    case4 = TreeNode(1,
                     TreeNode(2, TreeNode(4, TreeNode(8), TreeNode(9)), TreeNode(5, TreeNode(10))),
                     TreeNode(3, TreeNode(6), TreeNode(7)))
    assert getLastNode(case4).val == 1

1.2.2 完全二叉树的连续性性质

1.2.2.1 二叉树的完全性检验

958. 二叉树的完全性检验

class Solution:
    def isCompleteTree(self, root: TreeNode) -> bool:
        i = 0
        nodes = [(root, 1)]
        while i < len(nodes):
            node, v = nodes[i]
            i += 1
            if node:
                nodes.append([node.left, v * 2])
                nodes.append([node.right, v * 2 + 1])
        return len(nodes) == nodes[-1][1]
1.2.2.2 完全二叉树插入器

919. 完全二叉树插入器

class CBTInserter:

    def __init__(self, root: TreeNode):
        self.root = root
        queue = [root]
        self.heap = []
        while queue:
            top = queue.pop(0)
            self.heap.append(top)
            if top.left:
                queue.append(top.left)
            if top.right:
                queue.append(top.right)

    def insert(self, v: int) -> int:
        node = TreeNode(v)
        self.heap.append(node)
        n = len(self.heap)
        pid = n // 2 - 1
        parent = self.heap[pid]
        if n % 2:
            parent.right = node
        else:
            parent.left = node
        return parent.val

    def get_root(self) -> TreeNode:
        return self.root

1.3 二叉搜索树(BST)

1.3.1. BST的增删改查判断

手把手带你刷二叉搜索树(第二期)

手把手带你刷通二叉搜索树(第三期)

1.3.1.1. BST的查找 ⭐️

700. 二叉搜索树中的搜索

class Solution:
    def searchBST(self, root: TreeNode, val: int) -> TreeNode:
        if root is None or root.val == val:
            return root
        if root.val < val:
            return self.searchBST(root.right, val)
        else:
            return self.searchBST(root.left, val)
1.3.1.2. BST的插入 ⭐️⭐️

701. 二叉搜索树中的插入操作

class Solution:
    def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode:
        if root is None:
            return TreeNode(val)
        if root.val < val:
            root.right = self.insertIntoBST(root.right, val)
        else:
            root.left = self.insertIntoBST(root.left, val)
        return root
1.3.1.3. BST的删除 ⭐️⭐️⭐️⭐️

450. 删除二叉搜索树中的节点

  1. 叶子结点(0个孩子) → 当场去世
  2. 1个孩子 → 让孩子接替自己位置
  3. 两个孩子 → 找到左子树中最大的结点[ 右子树最小结点 ] 接替自己

class Solution:
    def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
        if root is None:
            return None
        if root.val == key:
            # == 情况1 == & == 情况2 ==
            if root.left is None:
                return root.right
            if root.right is None:
                return root.left
            # == 情况3 ==
            # 找到右子树的最小结点,即右子树不断往左遍历的最后一个结点
            min_node = self.find_min(root.right) 
            # 【这个结点】和【待删除结点】做swap,但具体的操作形式为赋值:
            # 【待删除结点】.val = 【右子树最小结点】.val
            root.val = min_node.val
            # 转化为子问题:删除【右子树最小结点】
            root.right = self.deleteNode(root.right, min_node.val)
        elif root.val > key: # 左右顺序写错
            root.left = self.deleteNode(root.left
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值