LeetCode之树

141 阅读19分钟

一、递归

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

1.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
  • 迭代法:
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        stack = []
        if root is not None:
            stack.append((1, root))
        
        depth = 0
        while stack != []:
            current_depth, root = stack.pop()
            if root is not None:
                depth = max(depth, current_depth)
                stack.append((current_depth + 1, root.left))
                stack.append((current_depth + 1, root.right))
        
        return depth

1.2 平衡树

给定一个二叉树,判断它是否是高度平衡的二叉树。 本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        if not root: return True
        return abs(self.depth(root.left) - self.depth(root.right)) <= 1 and \
            self.isBalanced(root.left) and self.isBalanced(root.right)

    def depth(self, root):
        if not root: return 0
        return max(self.depth(root.left), self.depth(root.right)) + 1
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        self.result = True
        def maxDepth(root):
            if not root:
                return 0
            l = maxDepth(root.left)
            r = maxDepth(root.right)
            if abs(l - r) > 1:
                self.result = False
            return 1 + max(l, r)
        
        maxDepth(root)
        return self.result

1.3 两结点的最长路径 / 二叉树的直径

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

Input:

         1
        / \
       2   3
      / \
     4   5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
class Solution:
    def diameterOfBinaryTree(self, root: TreeNode) -> int:
        self.max_val = 0

        def depth(root):
            if not root: return 0
            left_depth = depth(root.left)
            right_depth = depth(root.right)
            self.max_val = max(self.max_val, left_depth + right_depth)
            return max(left_depth, right_depth) + 1

        depth(root)
        return self.max_val

1.4 翻转树

输入:
     4
   /   \
  2     7
 / \   / \
1   3 6   9

输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1
class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root: return None
        root.left, root.right = self.invertTree(root.right), self.invertTree(root.left)
        return root

1.5 归并两棵树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例 1:

输入: 
	Tree 1                     Tree 2                  
          1                         2                             
         / \                       / \                            
        3   2                     1   3                        
       /                           \   \                      
      5                             4   7                  
输出: 
合并后的树:
	     3
	    / \
	   4   5
	  / \   \ 
	 5   4   7
注意: 合并必须从两个树的根节点开始。
class Solution:
    def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode:
        if not t1 and not t2: return None
        if not t1: return t2
        if not t2: return t1
        root = TreeNode(t1.val + t2.val)
        root.left = self.mergeTrees(t1.left, t2.left)
        root.right = self.mergeTrees(t1.right, t2.right)
        return root

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

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

示例: 
给定如下二叉树,以及目标和 sum = 22,
              5
             / \
            4   8
           /   / \
          11  13  4
         /  \      \
        7    2      1
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。
class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        if not root: 
            return False
        if not root.left and not root.right and root.val == sum:
            return True
        return self.hasPathSum(root.left, sum - root.val) or self.hasPathSum(root.right, sum - root.val)

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

给定一个二叉树,它的每个结点都存放着一个整数值。 找出路径和等于给定数值的路径总数。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。 二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

示例:
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1

返回 3。和等于 8 的路径有:
1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11
class Solution:
    def pathSum(self, root: TreeNode, sum: int) -> int:
        if not root: return 0

        def pathSumStartWithRoot(root, sum):
            if not root: return 0
            ret = 0
            if root.val == sum:
                ret += 1
            ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val)
            return ret

        ret = pathSumStartWithRoot(root, sum) + self.pathSum(root.left, sum) + self.pathSum(root.right, sum)
        return ret

1.8 子树

给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。

示例 1:
给定的树 s:

     3
    / \
   4   5
  / \
 1   2
 
给定的树 t:
   4 
  / \
 1   2
返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。

示例 2:
给定的树 s:
     3
    / \
   4   5
  / \
 1   2
    /
   0
   
给定的树 t:
   4
  / \
 1   2
返回 false
class Solution:
    def isSubtree(self, s: TreeNode, t: TreeNode) -> bool:
        if not s: return False

        def isSubtreeWithRoot(s, t):  # 树 s、t 是否完全相同
            if not t and not s: return True
            if not t or not s: return False
            if t.val != s.val: return False
            return isSubtreeWithRoot(s.left, t.left) and isSubtreeWithRoot(s.right, t.right)

        return isSubtreeWithRoot(s, t) or self.isSubtree(s.left, t) or self.isSubtree(s.right, t)

1.9 对称二叉树

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

    1
   / \
  2   2
 / \ / \
3  4 4  3
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root: return True

        def recur(t1, t2):
            if not t1 and not t2: return True
            if not t1 or not t2: return False
            if t1.val != t2.val: return False
            return recur(t1.left, t2.right) and recur(t1.right, t2.left)

        return recur(root.left, root.right)

1.10 最小路径 / 二叉树的最小深度

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

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回它的最小深度  2.
class Solution:
    def minDepth(self, root: TreeNode) -> int:
        if not root: return 0
        left = self.minDepth(root.left)
        right = self.minDepth(root.right)
        if left == 0 or right == 0: return left + right + 1
        return min(left, right) + 1

1.11 统计左子节点的和

计算给定二叉树的所有左叶子之和。

示例:
    3
   / \
  9  20
    /  \
   15   7
在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
class Solution:
    def sumOfLeftLeaves(self, root: TreeNode) -> int:
        if not root: return 0

        def isLeaf(node):
            if not node: return False
            return not node.left and not node.right

        if isLeaf(root.left): 
            return root.left.val + self.sumOfLeftLeaves(root.right)
        return self.sumOfLeftLeaves(root.left) + self.sumOfLeftLeaves(root.right)

1.12 相同节点值得最大路径长度

给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。 注意:两个节点之间的路径长度由它们之间的边数表示。

输入:
              5
             / \
            4   5
           / \   \
          1   1   5
输出:
2
class Solution:
    def longestUnivaluePath(self, root: TreeNode) -> int:
        self.path = 0

        def dfs(root):
            if not root: return 0
            left = dfs(root.left)
            right = dfs(root.right)
            left_path = left + 1 if root.left and root.left.val == root.val else 0
            right_path = right + 1 if root.right and root.right.val == root.val else 0
            self.path = max(self.path, left_path + right_path)
            return max(left_path, right_path)
        
        dfs(root)
        return self.path

1.13 间隔遍历 / 打家劫舍(III)

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:
输入: [3,2,3,null,3,null,1]
     3
    / \
   2   3
    \   \ 
     3   1
输出: 7 
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

示例 2:
输入: [3,4,5,1,3,null,1]
     3
    / \
   4   5
  / \   \ 
 1   3   1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

对于每一个节点,都只有选和不选两种情况。我们每次考虑一棵子树,那么根只有两种情况,选和不选 (我们让 dp[0] 表示不选, dp[1] 表示选)。

class Solution:
    def rob(self, root: TreeNode) -> int:
        def dp(node):
            if not node: return [0, 0]
            l = dp(node.left)
            r = dp(node.right)
            # max(max(左不抢+右抢, 左抢+右不抢, 左不抢+右不抢, 左抢+右抢), 抢根节点+左不抢+右不抢)
            # 等价为 max(max(左不抢,左抢) + max(右不抢,右抢),抢根节点+左不抢+右不抢)
            return [max(l) + max(r), node.val + l[0] + r[0]]
        
        return max(dp(root))

1.14 找出二叉树中最小的节点

给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个

给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。

示例 1:
输入: 
    2
   / \
  2   5
     / \
    5   7
输出: 5
说明: 最小的值是 2 ,第二小的值是 5 。

示例 2:
输入: 
    2
   / \
  2   2
输出: -1
说明: 最小的值是 2, 但是不存在第二小的值。

一个节点要么具有 0 个或 2 个子节点,如果有子节点,那么根节点是最小的节点。

思路:寻找左右子树中的最小节点。

class Solution:
    def findSecondMinimumValue(self, root: TreeNode) -> int:
        if not root: return -1
        if not root.left and not root.right: return -1
        left_val = root.left.val
        right_val = root.right.val

        if left_val == root.val:  # 说明 left_val 不可能为第二小的节点,需要去找左子树中最小节点
            left_val = self.findSecondMinimumValue(root.left)
        if right_val == root.val:  # 说明 right_val 不可能为第二小的节点,需要去找右子树中最小节点
            right_val = self.findSecondMinimumValue(root.right)
        if left_val != -1 and right_val != -1:  # 均不为 -1 说明,说明左右子树均有最小节点
            return min(left_val, right_val)
        if left_val != -1:  # 说明 right_val = -1,右子树无最小节点,第二小节点为左子树的最小节点
            return left_val
        return right_val  # 说明 left_val = -1,左子树无最小节点,第二小节点为右子树的最小节点

二、层次遍历

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

2.1 一棵树每层节点的平均值

给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。

示例 1:
输入:
    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]:
        ret = []
        if not root: return ret
        queue = collections.deque()
        queue.append(root)

        while queue:
            cnt = len(queue)
            sum = 0.0
            for _ in range(cnt):  # 求本层所有节点的和,并将下层左右节点添加到队列中
                node = queue.popleft()
                sum += node.val
                if node.left: queue.append(node.left)
                if node.right: queue.append(node.right)
            ret.append(sum / cnt)
        return ret

2.2 得到左下角的节点

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

示例 1:
输入:
    2
   / \
  1   3
输出:
1

示例 2:
输入:
        1
       / \
      2   3
     /   / \
    4   5   6
       /
      7
输出:
7
class Solution:
    def findBottomLeftValue(self, root: TreeNode) -> int:
        queue = collections.deque()
        queue.append(root)
        while queue:  # 一层层的寻找
            root = queue.popleft()
            if root.right: queue.append(root.right)
            # 后添加左节点,则左节点后出来,当最后一层左右节点均存在时,左节点为 root
            if root.left: queue.append(root.left)
        return root.val

三、前中后序遍历

    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 实现

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

① 前序:根左右

def preorder(root):
    if root:
        print(root.val)
        preorder(root.left)
        preorder(root.right)

② 中序:左根右

def inorder(root):
    if root:
        inorder(root.left)
        print(root.val)
        inorder(root.right)

③ 后序:左右根

def postorder(root):
    if root:
        postorder(root.left)
        postorder(root.right)
        print(root.val)

3.1 非递归实现二叉树的前序遍历

给定一个二叉树,返回它的 前序 遍历。

示例:
输入: [1,null,2,3]  
   1
    \
     2
    /
   3 
输出: [1,2,3]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        ret = []
        stack = [root]
        while stack:
            node = stack.pop()
            if not node: continue
            ret.append(node.val)  # 根左右
            stack.append(node.right)  # 先右后左,保证左子树先遍历
            stack.append(node.left)
        return ret

3.2 非递归实现二叉树后序遍历

给定一个二叉树,返回它的 后序 遍历。

示例:
输入: [1,null,2,3]  
   1
    \
     2
    /
   3 
输出: [3,2,1]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?

前序遍历为 root -> left -> right,后序遍历为 left -> right -> root。可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        ret = []
        stack = [root]
        while stack:
            node = stack.pop()
            if not node: continue
            ret.append(node.val)
            stack.append(node.left)
            stack.append(node.right)
        return ret[::-1]

3.3 非递归实现二叉树的中序遍历

给定一个二叉树,返回它的中序 遍历。

示例:
输入: [1,null,2,3]
   1
    \
     2
    /
   3
输出: [1,3,2]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        ret = []
        if not root: return ret
        stack = []
        cur = root
        while cur or stack:
            while cur:  
                stack.append(cur)
                cur = cur.left  
            node = stack.pop()
            ret.append(node.val)
            cur = node.right
        return ret

四、BST (Binary Search Tree)

二叉查找树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点

二叉查找树中序遍历有序。

4.1 修剪二叉查找树

给定一个二叉搜索树,同时给定最小边界 L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在 [L, R] 中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。

输入: 
    1
   / \
  0   2
L = 1
R = 2
输出: 
    1
      \
       2
  • 当 node.val > R,那么修剪后的二叉树必定出现在节点的左边
  • 当 node.val < L,那么修剪后的二叉树出现在节点的右边
  • 否则,我们将会修剪树的两边。
class Solution:
    def trimBST(self, root: TreeNode, L: int, R: int) -> TreeNode:
        def trim(node):
            if not node:
                return None
            elif node.val > R:  # 修剪后的二叉树必定出现在节点的左边
                return trim(node.left)
            elif node.val < L:  # 修剪后的二叉树出现在节点的右边
                return trim(node.right)
            else:  # 修剪树的两边
                node.left = trim(node.left)
                node.right = trim(node.right)
                return node

        return trim(root)

4.2 寻找二叉查找树的第 k 个元素

给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。

说明: 你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。

示例 1:
输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 1

示例 2:
输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 3

使用中序遍历:

  • 递归法:
class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
        def inorder(root):
            return inorder(root.left) + [root.val] + inorder(root.right) if root else []
        return inorder(root)[k - 1]
  • 迭代法
class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
        stack = []
        while True:
            while root:
                stack.append(root)
                root = root.left
            root = stack.pop()
            k -= 1
            if k == 0:
                return root.val
            root = root.right

4.3 把二叉查找树每个节点的值都加上比它大的节点的值

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。

输入: 原始二叉搜索树:
              5
            /   \
           2     13

输出: 转换为累加树:
             18
            /   \
          20     13

先遍历右子树:

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

4.4 二叉查找树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
       _______6______
      /                \
  ___2__             ___8__
 /      \           /      \
0        4         7        9
        /  \
       3   5
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。
  • 递归法:
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if root.val > p.val and root.val > q.val:
            return self.lowestCommonAncestor(root.left, p, q)
        elif root.val < p.val and root.val < q.val:
            return self.lowestCommonAncestor(root.right, p, q)
        else:
            return root
  • 迭代法:
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        while root:
            if root.val > p.val and root.val > q.val:
                root = root.left
            elif root.val < p.val and root.val < q.val:
                root = root.right
            else:
                return root

4.5 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
       _______3______
      /              \
  ___5__           ___1__
 /      \         /      \
6        2       0        8
        /  \
       7    4
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        # 1、终止条件:
        # root 为空时,直接返回空,即返回 root
        # root 等于 p 或 q 时,直接返回 root
        if not root or root == p or root == q:
            return root
        # 2、地推工作
        # 开启递归左子节点,返回值记为 left
        # 开启递归右子节点,返回值记为 right
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        # 3、返回值:
        if not left:  # left 不为空
            return right
        if not right:  # right 不为空
            return left
        return root  # 同时不为空

返回值: 根据 left 和 right ,可展开为四种情况;

  1. 当 left 和 right 同时为空 :说明 root 的左 / 右子树中都不包含 p,q ,返回 null;
  2. 当 left 和 right **同时不为空 **:说明 p, q 分别在 root 的 异侧 (分别在 左 / 右子树),因此 root 为最近公共祖先,返回 root ;
  3. 当 left 为空 ,right 不为空 :p,q 都不在 root 的左子树中,直接返回 right 。具体可分为两种情况:
    • p,q 其中一个在 root 的 右子树 中,此时 right 指向 p(假设为 p );
    • p,q 两节点都在 root 的 右子树 中,此时的 right 指向 最近公共祖先节点
  4. 当 left 不为空 , right 为空 :与情况 3. 同理;

4.6 从有序数组中构造二叉查找树

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
右边: 0        左边:  0
     / \             / \
   -3   9          -10  5
   /   /             \   \
 -10  5              -3   9
  • 始终选择中间位置左边元素作为根结点
class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:        
        def helper(left, right):
            if left > right:
                return None

            # always choose left middle node as a root
            p = (left + right) // 2

            # inorder traversal: left -> node -> right
            root = TreeNode(nums[p])
            root.left = helper(left, p - 1)
            root.right = helper(p + 1, right)
            return root
        
        return helper(0, len(nums) - 1)
  • 始终选择中间位置右边元素作为根结点
class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:        
        def helper(left, right):
            if left > right:
                return None
            
            # always choose right middle node as a root
            p = (left + right) // 2 
            if (left + right) % 2:
                p += 1 

            # inorder traversal: left -> node -> right
            root = TreeNode(nums[p])
            root.left = helper(left, p - 1)
            root.right = helper(p + 1, right)
            return root
        
        return helper(0, len(nums) - 1)

4.7 根据有序链表构造平衡的二叉查找树

给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

给定的有序链表: [-10, -3, 0, 5, 9],
一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:
      0
     / \
   -3   9
   /   /
 -10  5
class Solution:
    def sortedListToBST(self, head: ListNode) -> TreeNode:
        self.temp = []
        while head:
            self.temp.append(head.val)
            head = head.next

        def BST(left, right):
            if left > right: return
            mid = left + (right - left) // 2
            root = TreeNode(self.temp[mid])
            root.left = BST(left, mid - 1)
            root.right = BST(mid + 1, right)
            return root

        return BST(0, len(self.temp) - 1)
  • 使用递归模拟中序遍历过程,建立节点的顺序即与链表元素顺序一一对应,bottom-up(自底向上) 建立树,最终返回根节点。
class Solution:
    def sortedListToBST(self, head: ListNode) -> TreeNode:
        self.head = head
        n = 0
        while head:
            head = head.next
            n += 1
        
        def toBST(left, right):
            if left > right: return
            mid = left + (right - left) // 2
            
            node_left = toBST(left, mid - 1)
            root = TreeNode(self.head.val)
            self.head = self.head.next
            root.left = node_left
            root.right = toBST(mid + 1, right)
            return root
        
        return toBST(0, n - 1)

4.8 在二叉查找树中寻求两个节点,使它们的和为一个给定值

给定一个二叉搜索树和一个目标结果,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。

输入: 
    5
   / \
  3   6
 / \   \
2   4   7
Target = 9
输出: True

使用中序遍历得到有序数组之后,再利用双指针对数组进行查找。

应该注意到,这一题不能用分别在左右子树两部分来处理这种思想,因为两个待求的节点可能分别在左右子树中。

class Solution:
    def findTarget(self, root: TreeNode, k: int) -> bool:
        self.temp = []

        def inorder(root):
            if root:
                inorder(root.left)
                self.temp.append(root.val)
                inorder(root.right)

        inorder(root)
        l, r = 0, len(self.temp) - 1
        while l < r:
            if self.temp[l] + self.temp[r] == k:
                return True
            elif self.temp[l] + self.temp[r] > k:
                r -= 1
            else:
                l += 1
        return False

4.9 在二叉查找树中查找两个节点之差的最小绝对值

给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。

输入:
   1
    \
     3
    /
   2
输出:1
解释:最小绝对差为 1,其中 2 和 1 的差的绝对值为 1(或者 2 和 3)。
  • 利用中序遍历将二叉树转换为有序的数组
class Solution:
    def getMinimumDifference(self, root: TreeNode) -> int:
        self.temp = []
        def inorder(root):
            if root:
                inorder(root.left)
                self.temp.append(root.val)
                inorder(root.right)
        
        inorder(root)
        minval = float("inf")
        for i in range(1, len(self.temp)):
            minval = min(minval, self.temp[i] - self.temp[i - 1])
        return minval
    
    
class Solution:
    def getMinimumDifference(self, root: TreeNode) -> int:
        def preorder(root):
            if not root:
                return []
            else:
                return preorder(root.left) + [root.val] + preorder(root.right)
        num = preorder(root)
        min_ = float('inf')
        for i in range(len(num) - 1):
            min_ = min(min_, num[i+1] - num[i])
        return min_
  • 利用二叉查找树的中序遍历为有序的性质,计算中序遍历中临近的两个节点之差的绝对值,取最小值。
class Solution:
    def getMinimumDifference(self, root: TreeNode) -> int:
        self.min_val = float("inf")
        self.pre_node = None
        def inorder(root):
            if root:
                inorder(root.left)
                if self.pre_node:
                    self.min_val = min(self.min_val, root.val - self.pre_node.val)
                self.pre_node = root
                inorder(root.right)
        
        inorder(root)
        return self.min_val

4.10 寻找二叉查找树中出现次数最多的值

给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。 假定 BST 有如下定义:

结点左子树中所含结点的值小于等于当前结点的值 结点右子树中所含结点的值大于等于当前结点的值 左子树和右子树都是二叉搜索树

给定 BST [1,null,2,2],
   1
    \
     2
    /
   2
返回[2].

答案可能不止一个,也就是有多个值出现的次数一样多。

class Solution:
    def findMode(self, root: TreeNode) -> List[int]:
        if not root: return []
        self.table = {}
        
        def inorder(root):
            if root:
                inorder(root.left)
                if root.val not in self.table:
                    self.table[root.val] = 1
                else:
                    self.table[root.val] += 1
                inorder(root.right)
        
        inorder(root)
        res = []
        m = max(self.table.values())
        for k, v in self.table.items():
            if v == m:
                res.append(k)
        return res