【算法题】树系列

本文详细介绍了各种与二叉树相关的算法题目,包括深度、节点、路径、树的操作、遍历、二叉搜索树和字典树等多个方面。通过实例解析了如二叉树的最大深度、最小深度、右视图、层平均值、路径和等概念,并探讨了如何利用DFS和BFS进行遍历,以及二叉搜索树的特性及其应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注:题号对应leetcode题号,可以在官网搜索题号查询

目录

深度有关

104. 二叉树的最大深度&面试题55 

111. 二叉树的最小深度

节点有关

199. 二叉树的右视图

404. 左叶子之和

68 - II 二叉树的最近公共祖先   

637. 二叉树的层平均值-BFS

513. 找树左下角的值-BFS

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

路径有关

112. 判断路径和是否等于一个数

437.统计路径和等于一个数的路径数量

124 二叉树的最大路径和 

34 二叉树中和为某一值的路径 & 113

543 二叉树的直径-两节点最长路径

687. 最长同值路径

树的操作

面试题27:二叉树镜像&226 翻转二叉树

617. 合并二叉树

101&面试题28. 判断对称的二叉树

110&55 - II. 平衡二叉树-dfs 

面试题7.重建二叉树/还原二叉树 &105 

37.序列化二叉树&297. 二叉树的序列化与反序列化 +1

遍历

144. 二叉树的前序遍历-DFS

94. 二叉树的中序遍历-DFS

145. 二叉树的后序遍历

32 I.从上到下打印二叉树-层次遍历-队列 

32.II 从上到下打印二叉树II-循环

32.III 从上到下打印二叉树 III- flag insert & 103. 二叉树的锯齿形层次遍历 

子树

572. 子树判断 &面试题26 

二叉搜索树

255. 验证前序遍历序列二叉搜索树

814.二叉树剪枝

669. 修剪二叉搜索树

108. 将有序数组转换为二叉搜索树-二分查找

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

54. 二叉搜索树的第k大节点-中序遍历

68 - I 二叉搜索树的最近公共祖先 

653. 寻找两个节点和给定值-双指针

530. 二叉搜索树的最小绝对差

538. 把二叉搜索树转换为累加树

501. 二叉搜索树中的众数

33 二叉搜索树的后续遍历序列

36. 二叉搜索树与双向链表

99 恢复二叉搜索树

面试题 04.05. 合法二叉搜索树 &98

96. 不同的二叉搜索树-动态规划

95. 不同的二叉搜索树 II

字典树

208. 实现 Trie (前缀树)

677. 键值映射

211. 添加与搜索单词 - 数据结构设计

212. 单词搜索 II

421. 数组中两个数的最大异或值


树大多采用递归

树的定义

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

层次遍历

使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。

 

递归

一棵树要么是空树,要么有两个指针,每个指针指向一棵树。树是一种递归结构,很多树的问题可以使用递归来处理。

 

前中后序遍历

1

  / \

  2  3

 / \  \

4  5  6

层次遍历顺序:[1 2 3 4 5 6] 前序遍历顺序:[1 2 4 5 3 6] 中序遍历顺序:[4 2 5 1 3 6] 后序遍历顺序:[4 5 2 6 3 1]

层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。

前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。

① 前序

void dfs(TreeNode root){

    visit(root);

    dfs(root.left);

    dfs(root.right);

}

② 中序

void dfs(TreeNode root){

    dfs(root.left);

    visit(root);

    dfs(root.right);

}

③ 后序

void dfs(TreeNode root){

    dfs(root.left);

    dfs(root.right);

    visit(root);

}

BST

主要利用 BST 中序遍历有序的特点。

 

深度有关

104. 二叉树的最大深度&面试题55 

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

给定二叉树 [3,9,20,null,null,15,7],

    3

   / \

  9  20

    /  \

   15   7

返回它的最大深度 3 。

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

111. 二叉树的最小深度

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root: return 0
        left = self.minDepth(root.left)
        right = self.minDepth(root.right)
        if not left or not right: return left+right+1
        return min(left,right)+1

节点有关

199. 二叉树的右视图

给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

输入: [1,2,3,null,5,null,4]

输出: [1, 3, 4]

解释:

   1            <---

 /   \

2     3         <---

 \     \

  5     4       <---

class Solution:
    def rightSideView(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        res = []
        tmp_layer = collections.deque()  # 还是BFS用到的queue
        tmp_layer.append(root)  # 先将初始节点压入
        while len(tmp_layer) > 0:
            count = 0  # 计数,用来记录是某一层的第几个元素
            next_layer = []
            while len(tmp_layer) > 0:  # 处理某一层的节点
                tmp_node = tmp_layer.popleft()
                if count == 0:  # 根据题意只要最右侧的元素,所以根据下面先压入队列的是右子树,后压入左子树,我们只要队列中的第一个元素
                    res.append(tmp_node.val)
                count += 1
                # 将某个节点的右左子树压入
                if tmp_node.right is not None:
                    next_layer.append(tmp_node.right)
                if tmp_node.left is not None:
                    next_layer.append(tmp_node.left)
            tmp_layer = collections.deque(next_layer)  # 更新下一层的tmp
        return res

404. 左叶子之和

class Solution:
    def sumOfLeftLeaves(self, root: TreeNode) -> int:
        if not root:
            return 0
        if root and root.left and not root.left.left and not root.left.right:
            return root.left.val+self.sumOfLeftLeaves(root.right)
        return self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)

68 - II 二叉树的最近公共祖先   

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if not root or root.val == p.val or root.val == q.val: return root
        l = self.lowestCommonAncestor(root.left,  p , q)
        r = self.lowestCommonAncestor(root.right,  p , q)
        return root if l and r else l or r

637. 二叉树的层平均值-BFS

一棵树每层节点的平均数

输入:

    3

   / \

  9  20

    /  \

   15   7

输出: [3, 14.5, 11]

解释:

第0层的平均值是 3,  第1层是 14.5, 第2层是 11. 因此返回 [3, 14.5, 11].

class Solution:
    def averageOfLevels(self, root: TreeNode) -> List[float]:
        if not root: return []
        ans = []
        queue = [root]
        while queue:
            n = len(queue)
            sum = 0
            for i in range(n):
                node = queue.pop(0)
                sum += node.val
                if node.left: queue.append(node.left)
                if node.right: queue.append(node.right)
            ans.append(sum/n)
        return ans

513. 找树左下角的值-BFS

给定一个二叉树,在树的最后一行找到最左边的值。

class Solution:
    def findBottomLeftValue(self, root: TreeNode) -> int:
        queue = [root]
        while queue:
            node = queue.pop(0)
            if node.right: queue.append(node.right)
            if node.left: queue.append(node.left)
        return node.val

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

给出一个完全二叉树,求出该树的节点个数。

如果让你数一下一棵普通二叉树有多少个节点,这很简单,只要在二叉树的遍历框架上加一点代码就行了。

这个算法的时间复杂度应该是 O(logN*logN)

首先要明确一下两个关于二叉树的名词「完全二叉树」和「满二叉树」。

如果是一个普通二叉树,显然只要向下面这样遍历一边即可,时间复杂度 O(N):

def count(self,root):
    if not root: return 0
    return 1+self.count(root.left)+self.count(root.right)

那如果是一棵二叉树,节点总数就和树的高度呈指数关系,时间复杂度 O(logN):

def count(self,root):
    h = 0
    while root:
        root = root.left
        h += 1 
    return 2**h-1
        #节点总数就是 2^h - 1

完全二叉树比普通二叉树特殊,但又没有满二叉树那么特殊,计算它的节点总数,可以说是普通二叉树和完全二叉树的结合版,降低时间复杂度

def count(self,root):
    hl,hr = 0,0
    left,right = root,root
    while left:
        left = left.left
        hl +=1
    while right:
        right = right.right
        hr += 1
    if hl == hr: #如果左右子树的高度相同,则是一棵满二叉树
        return 2**hl-1
     #如果左右高度不同,则按照普通二叉树的逻辑计算
    return 1+self.count(root.right)+self.count(root.right)

这个算法的时间复杂度是 O(logN*logN),这是怎么算出来的呢?

之前的 while 需要 logN 的时间,最后要 O(N) 的时间向左右子树递归:

return 1 + countNodes(root.left) + countNodes(root.right);

关键点在于,这两个递归只有一个会真的递归下去,另一个一定会触发hl == hr而立即返回,不会递归下去

完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。

由于完全二叉树的性质,其子树一定有一棵是满的,所以一定会触发hl == hr,只消耗 O(logN) 的复杂度而不会继续递归。

算法的递归深度就是树的高度 O(logN),每次递归所花费的时间就是 while 循环,需要 O(logN),所以总体的时间复杂度是 O(logN*logN)。

路径有关

112. 判断路径和是否等于一个数

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

给定如下二叉树,以及目标和 sum = 22,

              5

             / \

            4   8

           /   / \

          11  13  4

         /  \      \

        7    2      1

返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。

class Solution:
    def hasPathSum(self, root, sum):
        if not root:
            return False
        sum -= root.val
        if not root.left and not root.right:  # if reach a leaf
            return sum == 0
        return self.hasPathSum(root.left, sum) or self.hasPathSum(root.right, sum)

437.统计路径和等于一个数的路径数量

class Solution:
    def pathSum(self, root: TreeNode, sum: int) -> int:
        if not root: return 0
        ret = self.pathSumStartWithRoot(root,sum)+self.pathSum(root.left,sum)+self.pathSum(root.right,sum)
        return ret
    def pathSumStartWithRoot(self,root,sum):
        if not root: return 0
        ret = 0
        if root.val==sum: 
            ret+= 1
        ret += self.pathSumStartWithRoot(root.left,sum-root.val)+self.pathSumStartWithRoot(root.right,sum-root.val)
        return ret
        

124 二叉树的最大路径和 

给定一个非空二叉树,返回其最大路径和。

本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

输入: [-10,9,20,null,null,15,7]

   -10

   / \

  9  20

    /  \</

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值