BST二分搜索树题目总结(都是很经典的递归)

315. Count of Smaller Numbers After Self 计算右侧小于当前元素的个数(逆序数)

图源:慕课网

1. 原始题目

计算右侧小于当前元素的个数

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是  nums[i] 右侧小于 nums[i] 的元素的数量。

示例:

输入: [5,2,6,1]
输出: [2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.

2. 思路

暴力法,从左往右循环+内部循环:O(n^2)复杂度。正确解法:二分搜索树BST算法

我们考虑能否在二分搜索树时每插入一个元素,就输出一个结果?比如题目中的例子:插入5,输出2;插入2,输出2...但是发现最开始的空树,我们插入5如何输出2?因为后面的元素还没有输入进来,没法确定这个2。自然想到将原数组取反:【1,6,2,5】,这时插入1,输出0;插入6输出1...(每插入一个数,就返回当前比该结点小的节点数目。)插入2,由于前面只有1比他小,输出1;最后插入5,前面有1和2比他小,输出2!

那如何利用BST呢?因为我们的解题思想是每插入一个数,就返回当前比该结点小的节点数目。而BST每个节点的左子树的结点数可不是当前比该节点小的节点数目吗?有了这一观察,就可以写一个BST了。

 

3. 实现

 1 class Node():
 2     def __init__(self, val):
 3         self.val = val
 4         self.count = 1       # 所有val值一样的结点数目
 5         self.leftsize = 0    # 左子树节点数目
 6         self.left=None
 7         self.right = None
 8         
 9         
10 class BST():
11     def __init__(self):
12         self.root = None          # 初始化为一个空树
13 def insert(self, root, val): # insert函数的含义:对根为root的结点插入结点val时返回的小于该节点的数目 14 if not root: 15 self.root = Node(val) 16 return 0 17 18 if root.val==val: # 如果当前要插入的结点值就等于根节点的值,那count数加1 19 root.count+=1 20 return root.leftsize # 返回的小于该节点的数目只能是其左子树的节点数了 21 22 if val<root.val: 23 root.leftsize+=1 # 子树结点数+1(重要) 24 if not root.left: # 左子树空,则加入该节点,返回0!因为当前没有比他小的节点 25 root.left = Node(val) 26 return 0 27 return self.insert(root.left, val) # 左子树非空,则递归在左孩子为根的子树插入该节点 28 29 if val>root.val: 30 if not root.right: # 右子树为空,则先插入该节点,返回的应该是根节点的重复元素数目+左子树的结点数 31 root.right = Node(val) 32 return root.count+root.leftsize 33 return root.count+root.leftsize+self.insert(root.right, val) # 右子树非空,同上递归就好 34 35 36 class Solution: 37 def countSmaller(self, nums): 38 nums = nums[::-1] # 先逆个序 39 bst = BST() 40 res = [] 41 for i in nums: 42 res.append(bst.insert(bst.root,i)) # 依次插入结点 43 return res[::-1]

总结:关于树的代码大多可用递归,值得注意的是:

  • 弄清楚递归函数的输入与输出
  • 终止条件和当前情况的考虑

 

235. Lowest Common Ancestor of a Binary Search Tree  二叉搜索树的最近公共祖先

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

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

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。

思路:f题目貌似很难,实则异常简单:只有三种情况!

  • 如果p和q同时小于root则在root的左子树中继续寻找。
  • 如果p和q同时大于root则在root的右子树中继续寻找。
  • 其余情况(如果p,q位于root的两侧,抑或是pq中有等于root的情况),那么其最近的公共祖先就是root!
 1 # Definition for a binary tree node.
 2 # class TreeNode:
 3 #     def __init__(self, x):
 4 #         self.val = x
 5 #         self.left = None
 6 #         self.right = None
 7 
 8 class Solution:
 9     def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
10         if not root:return root
11         if p.val<root.val and q.val<root.val:
12             return self.lowestCommonAncestor(root.left, p,q)
13         elif p.val>root.val and q.val>root.val:
14             return self.lowestCommonAncestor(root.right, p,q)
15         else:
16             return root

 

 

98. Validate Binary Search Tree  验证二叉搜索树  ****

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:
    2
   / \
  1   3
输出: true

示例 2:

输入:
    5
   / \
  1   4
     / \
    3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
     根节点的值为 5 ,但是其右子节点值为 4 。

这道题直观上看非常简单,你可以立马写下类似这样的代码:但这个代码是通不过全部测试的:

 1 # Definition for a binary tree node.
 2 # class TreeNode:
 3 #     def __init__(self, x):
 4 #         self.val = x
 5 #         self.left = None
 6 #         self.right = None
 7 
 8 class Solution:
 9     def isValidBST(self, root: TreeNode) -> bool:
10         if not root:return True
11         if not root.left and not root.right:return True
12         if not root.left:
13             return root.right.val>root.val
14         elif not root.right:
15             return root.left.val<root.val 
16         else:
17             return root.val>root.left.val and root.val<root.right.val\
18                    and self.isValidBST(root.left)\
19                    and self.isValidBST(root.right)

以上的代码将下图判定为ture:因为代码只判断了当前的左结点比他小,右结点比他大!并没有判断对于当前节点所有左结点都比他小,所有右结点都比他大!

正确做法是设置一个边界:对于每个节点都有一个上界和下届来保证当前节点合法:例如对于以根为5的子树而言,要保证5本身处于预定义的上下界(负无穷-正无穷)之中。并且5的左孩子要在的边界为(当前的下界,上界为5)。并且5的右孩子要在的边界为(5,当前的上界)。

正确解法如下:

 1 # Definition for a binary tree node.
 2 # class TreeNode:
 3 #     def __init__(self, x):
 4 #         self.val = x
 5 #         self.left = None
 6 #         self.right = None
 7 
 8 class Solution:
 9     def helper(self, root, lower, upper):
10         if not root:return True
11         if root.val<=lower or root.val>=upper:
12             return False
13         if not self.helper(root.left, lower, root.val):
14             return False
15         if not self.helper(root.right, root.val, upper):
16             return False
17         return True
18         
19         
20     def isValidBST(self, root: TreeNode) -> bool:
21         return self.helper(root,float('-inf'),float('inf'))
22         

上述递归可以很简答的扩展为迭代法。当然还有一种方法也非常直观!就是中序遍历!对于一颗二叉搜索树的中序遍历一定是递增的序列!!!同时题目里要求不可以有相同的元素,所以验证排序必须是严格递增,一个递归实现的中序遍历+验证严格递增的代码如下:

 1 class Solution:
 2     def inorder(self, root):
 3         if not root:return []
 4         res = []
 5         res+=self.inorder(root.left)
 6         res.append(root.val)
 7         res+=self.inorder(root.right)
 8         return res
 9         
10     def isValidBST(self, root: TreeNode) -> bool:
11         if not root:return True
12         res = self.inorder(root)
13         pre = res[0]
14         for curr in res[1:]:
15             if curr<=pre:
16                 return False
17             pre = curr
18         return True

一个更简洁的迭代方法如下:

 1 class Solution:
 2     def isValidBST(self, root):
 3         """
 4         :type root: TreeNode
 5         :rtype: bool
 6         """
 7         stack, inorder = [], float('-inf')
 8         
 9         while stack or root:
10             while root:
11                 stack.append(root)
12                 root = root.left
13             root = stack.pop()
14             # If next element in inorder traversal
15             # is smaller than the previous one
16             # that's not BST.
17             if root.val <= inorder:
18                 return False
19             inorder = root.val
20             root = root.right
21 
22         return True
View Code

 

 

450. Delete Node in a BST  删除二叉搜索树中的节点

虽然写过了,但是又一次掉入了坑里

 1 # Definition for a binary tree node.
 2 # class TreeNode:
 3 #     def __init__(self, x):
 4 #         self.val = x
 5 #         self.left = None
 6 #         self.right = None
 7 
 8 class Solution:
 9     def removeMin(self, root)->TreeNode:    # 删除最小值节点
10         if not root:return root
11         if not root.left:
12             return root.right
13         else:
14             root.left =  self.removeMin(root.left)    # 这次坑在这里,最初写的是return self.removeMin(root.left)
15             return root
16     
17     def minimum(self, root)->TreeNode:       # 找最小值节点
18         if not root:return root
19         if not root.left:
20             return root
21         else:
22             return self.minimum(root.left)
23         
24     def deleteNode(self, root: TreeNode, key: int) -> TreeNode:
25         if not root:return root
26         if key>root.val:
27             root.right = self.deleteNode(root.right, key)
28             return root
29         elif key<root.val:
30             root.left = self.deleteNode(root.left, key)
31             return root
32         else:
33             if not root.left:
34                 return root.right
35             elif not root.right:
36                 return root.left
37             else:
38                 newminNode = self.minimum(root.right)
39                 newminNode.right = self.removeMin(root.right)
40                 newminNode.left = root.left
41                 return newminNode

leetcode上的更简洁写法,是将case3进行了简化:即针对root的值等于当前key的情况下之分为两种情况。(我的分为了三种:无左孩子,无右孩子,无左右孩子)其代码如下:

 1 class Solution:
 2     def deleteNode(self, root, key):
 3         """
 4         :type root: TreeNode
 5         :type key: int
 6         :rtype: TreeNode
 7         """
 8         if not root:
 9             return
10         
11         # we always want to delete the node when it is the root of a subtree,
12         # so we handle left or right according to the val.
13         # if the node does not exist, we will hit the very first if statement and return None.
14         if key > root.val:
15             root.right = self.deleteNode(root.right, key)
16             
17         elif key < root.val:
18             root.left = self.deleteNode(root.left, key)
19         
20         # now the key is the root of a subtree
21         else:
22             # if the subtree does not have a left child, we just return its right child
23             # to its father, and they will be connected on the higher level recursion.
24             if not root.left:
25                 return root.right
26             
27             # if it has a left child, we want to find the max val on the left subtree to 
28             # replace the node we want to delete.
29             else:
30                 # try to find the max value on the left subtree
31                 tmp = root.left
32                 while tmp.right:
33                     tmp = tmp.right
34                     
35                 # replace
36                 root.val = tmp.val
37                 
38                 # since we have replaced the node we want to delete with the tmp, now we don't
39                 # want to keep the tmp on this tree, so we just use our function to delete it.
40                 # pass the val of tmp to the left subtree and repeat the whole approach.
41                 root.left = self.deleteNode(root.left, tmp.val)
42         
43         return root
View Code

 

108. Convert Sorted Array to Binary Search Tree 将有序数组转换为二叉搜索树

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定有序数组: [-10,-3,0,5,9],

一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

      0
     / \
   -3   9
   /   /
 -10  5

非常有趣,二叉树的逆过程!

 1 # Definition for a binary tree node.
 2 # class TreeNode:
 3 #     def __init__(self, x):
 4 #         self.val = x
 5 #         self.left = None
 6 #         self.right = None
 7 
 8 class Solution:
 9     def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
10         if not nums:return None
11         mid = int(len(nums)/2)
12         root = TreeNode(nums[mid])
13         l = nums[:mid]
14         r = nums[mid+1:]
15         root.left = self.sortedArrayToBST(l)
16         root.right = self.sortedArrayToBST(r)
17         return root

 

 

230. Kth Smallest Element in a BST  二叉搜索树中第K小的元素

直观的解法是直接中序遍历然后选择第k-1个元素就好。

 1 # Definition for a binary tree node.
 2 # class TreeNode:
 3 #     def __init__(self, x):
 4 #         self.val = x
 5 #         self.left = None
 6 #         self.right = None
 7 
 8 class Solution:
 9     def kthSmallest(self, root: TreeNode, k: int) -> int:
10         def inorder(r):
11             return inorder(r.left)+[r.val]+inorder(r.right) if r else []
12         return inorder(root)[k-1]

leetcode上还有许多更优解答:https://leetcode.com/problems/kth-smallest-element-in-a-bst/solution/

 

 

236. Lowest Common Ancestor of a Binary Tree  二叉树的最近公共祖先

注意此时不是二叉搜索树了!

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

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

例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉树中。
 1 # Definition for a binary tree node.
 2 # class TreeNode:
 3 #     def __init__(self, x):
 4 #         self.val = x
 5 #         self.left = None
 6 #         self.right = None
 7 
 8 class Solution:
 9     def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
10         if not root:return root
11         if root==p or root==q: return root    # 若根节点和p或q相等,那根就是公共组先
12         left = self.lowestCommonAncestor(root.left, p, q)   
13         right = self.lowestCommonAncestor(root.right, p, q)
14         if left and right:  # 如果左右都非空:只有一种可能:left和right就是p与q或q与p,此时组先就是root!!!
15             return root
16         elif left:      # 否则如果只有left非空,说明当前结点left不是p就是q!左子树里同时有pq
17             return left
18         elif right:     # 否则如果只有right非空,说明当前结点right不是p就是q!右子树里同时有pq
19             return right
20         else:
21             return None

 

posted @ 2019-04-29 10:52  三年一梦  阅读(854)  评论(0编辑  收藏  举报