Leetcod面试经典150题刷题记录 ------ 二叉搜索树篇
-
- 二叉搜索树性质
- [1. 二叉搜索树的最小绝对差](#1. 二叉搜索树的最小绝对差)
- [2. 二叉搜索树中第K小的元素](#2. 二叉搜索树中第K小的元素)
- [3. 验证二叉搜索树](#3. 验证二叉搜索树)
- 附加题
-
- [1. 不同的二叉搜索树](#1. 不同的二叉搜索树)
遇到二叉搜索树(BST)的题目,一旦用了sort()
直接挂掉面试,切记!
二叉搜索树性质
二叉搜索树的性质满足:
(1)左节点 > root > 右节点 (
局部性质
)(2)左子树所有节点 > root > 右子树所有节点 (
全局性质
,该性质包括局部性质,所以更重要)相当部分程序员写起上面的局部性质很容易,写全局性质的判断就容易犯病,不瞒你说,我也是。
1. 二叉搜索树的最小绝对差
题目链接: 二叉搜索树的最小绝对差 - leetcode
题目描述:给你一个二叉搜索树的根节点
root
,返回 树中任意两不同节点值之间的最小差值 。差值是一个正数,其数值等于两值之差的绝对值。
题目归纳:
解题思路:
解法: 验证二叉搜索树 - leetcode官方题解
脏乱差版本
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
# 返回二叉搜索树中,任意两个不同节点值之间的最小差值
# 性质
# (1)二叉搜索树。题目既然说了,那么肯定要用到该性质
# (2)任意两个不同节点值,强调了任意两个不同节点。但是既然是二叉搜索树了,拿右子树中的节点 - 左子树中的节点肯定不会是答案,所以这里的任意其实是带引号的"任意",不是绝对的"任意",是可以忽略一些情况的"任意"
# 以root节点为例,要查找的目标点一定是下面两种情况
# (1)左树的最右节点 = 左树的最大节点 = 中序遍历的前驱pre节点
# (2)右树的最左节点 = 右树的最大节点 = 中序遍历的后继post节点
# 最后递归搜索
class Solution:
def getMinDistance(self, root: Optional[TreeNode]) -> int:
if not root: return 0
# (1)查找左树的最'右'节点 = 左树的最大节点
LR = root.left
while LR and (LR.left or LR.right):
# 有右边找右边,没右边找左边再找右边
if LR.right:
LR = LR.right
else:
break
# (2)查找右树的最'左'节点 = 右树的最大节点
RL = root.right
while RL and (RL.left or RL.right):
if RL.left:
RL = RL.left
else:
break
left_result = 1e9
if LR: left_result = abs(root.val-LR.val)
right_result = 1e9
if RL: right_result = abs(root.val-RL.val)
return min(left_result, right_result)
def getMinimumDifference(self, root: Optional[TreeNode]) -> int:
result = 1e9
# 逐个遍历
queue = deque([root])
while queue:
size = len(queue)
for i in range(size):
node = queue.popleft()
if node.left: queue.append(node.left)
if node.right: queue.append(node.right)
dis = self.getMinDistance(node)
result = min(result, dis)
return result
优雅版本
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def __init__(self):
self.result = float('inf')
self.pre = None
def traversal(self, cur):
if cur is None:
return None
self.traversal(cur.left) # 左
if self.pre: # 中
self.result = min(self.result, cur.val - self.pre.val)
self.pre = cur # 记录前一个
self.traversal(cur.right) # 右
def getMinimumDifference(self, root):
self.traversal(root)
return self.result
2. 二叉搜索树中第K小的元素
题目链接: 二叉搜索树中第K小的元素 - leetcode
题目描述:给定一个二叉搜索树的根节点
root
,和一个整数k
,请你设计一个算法查找其中第k
个最小元素(从 1 开始计数)。
题目归纳:中序遍历BST成有序数组,然后再找到这个有序数组的第
k
个元素?NoNoNo。掌握递归转换成迭代的关键思想,即将"函数调用栈"明写在代码里。
解题思路:
解法: 二叉搜索树中第K小的元素 - leetcode官方题解中序遍历的迭代写法,注意,非递归!
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
# 这道题掌握两个知识点
# (1)中序遍历的迭代写法。即将函数调用栈明示出来,因为函数调用栈也是个栈,所有的递归写法都是可以转换为迭代版写法的,手动模拟函数调用栈即可。
# (2)二叉搜索树的中序遍历是有序的。
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
# 中序遍历,迭代版而非递归
stack = []
while root or stack:
# 相当于递归版写法的左子树遍历
while root: # 压栈方向是单一的,沿着二叉树的右上角->左下角方向压栈
stack.append(root)
root = root.left
root = stack.pop() # 遇到空就出栈
# if root: print(root.val)
k -= 1
if k == 0:
return root.val
root = root.right
3. 验证二叉搜索树
题目链接: 验证二叉搜索树 - leetcode
题目描述:给定一个二叉树的 根节点
root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
题目归纳:右视图 = 右边的侧视图
解题思路:
解法: 验证二叉搜索树 - leetcode官方题解(1) 从左到右层序遍历。记录层序遍历的最后一个node,即为右视图看到的第一个node。
经典错误(从局部性质推断全局性质)
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
# 这是一道21年的408考研真题,空节点和叶节点都是二叉搜索树
# 注意下面的写法是错误的,原因在于只判断了局部的性质,而忽略了全局的性质
if not root: return True
if not root.left and not root.right: return True
# (1)这个时候root肯定存在,左树或许存在,结合root与左树根节点,判断是不是二叉搜索树
if root and root.left and root.left.val < root.val:
return self.isValidBST(root.left)
else:
return False
# (2)这个时候root肯定存在,右树或许存在,结合root与右树根节点,判断是不是二叉搜索树
if root and root.right and root.val < root.right.val:
return self.isValidBST(root.right)
else:
return False
利用第1题的代码(有pre指针的那段)
python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def __init__(self):
self.pre = None
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
left = self.isValidBST(root.left)
if self.pre and self.pre.val >= root.val: # __比第1题加了这个判断__
return False
self.pre = root # 要遍历root.right了,这个时候记录pre节点
right = self.isValidBST(root.right)
return left and right # 两边都要是BST树
附加题
1. 不同的二叉搜索树
题目链接: 不同的二叉搜索树 - leetcode
题目描述:给你一个整数
n
,求恰由n
个节点组成且节点值从1
到n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
题目归纳:分治+动态规划
解题思路:
解法: 不同的二叉搜索树 - leetcode官方题解
python
class Solution:
def numTrees(self, n: int) -> int:
# 给一个整数n,求恰好由n个节点组成,且节点值从1到n,能够组成多少种不同的二叉搜索树。
#给定一个有序序列1...n,遍历数字i,将数字i作为root,1 ... (i-1)序列作为左子树,(i+1) ... n作为右子树,接着按照同样的方式递归构建左子树和右子树
# 在上述构建过程中,由于根root值不同,因此能保证每棵BST是唯一的。
# 采用动态规划来求解本题
# G(n): 长度为n的序列,所能构成的不同的BST树的个数。注意到 G(n) 和序列的内容无关,只和序列的长度有关
# F(i,n):以i为根、序列长度为n的不同BST的个数
# G(n) = sum_{1}^{n}F(i,n)
# 特别的: G(0) = 1 , G(1) = 1
# F(i,n) = G(i-1)*G(n-i)
# ==> G(n) = sum_{1}^{n}G(i-1)G(n-i)
G = [0] * (n+1)
G[0] = 1
G[1] = 1
for i in range(2, n+1):
for j in range(1, i+1):
G[i] += G[j-1] * G[i-j]
return G[n]