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
一个更简洁的迭代方法如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
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的情况下之分为两种情况。(我的分为了三种:无左孩子,无右孩子,无左右孩子)其代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
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