这篇文章记录了leetcode上目前遇到的二叉搜索树的题目,复习的时候可以和二叉树算法合集一块看。
查
700. 二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。
例如,给定二叉搜索树:
4
/ \
2 7
/ \
1 3
和值: 2
你应该返回如下子树:
2
/ \
1 3
在上述示例中,如果要找的值是 5,但因为没有节点值为 5,我们应该返回 NULL。
思路:
没啥好说的,典型递归,和二叉树中查看节点一样的结构。只不过这边二叉搜索树可以剪枝。
def searchBST(self, root, val):
"""
:type root: TreeNode
:type val: int
:rtype: TreeNode
"""
if root is None:
return None
if root.val == val:
return root
if val < root.val:
return self.searchBST(root.left, val)
else:
return self.searchBST(root.right, val)
剑指 Offer 54. 二叉搜索树的第k大节点
给定一棵二叉搜索树,请找出其中第k大的节点。
示例 1:
输入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
输出: 4
示例 2:
输入: root = [5,3,6,2,4,null,null,1], k = 3
5
/ \
3 6
/ \
2 4
/
1
输出: 4
思路:
方法一 大根堆
用python
库heapq
,在遍历过程中将节点加入到堆中,最后用heapq.nlargest
直接输出前k大的元素。遍历方式可以采取前序中序后序的任意一种。
时间复杂度
O
(
n
l
o
g
k
)
O(nlogk)
O(nlogk),空间复杂度
O
(
n
)
O(n)
O(n)
def kthLargest(self, root, k):
"""
用堆heapq,随便用哪个遍历方式
时间复杂度nlogk,空间复杂度n
"""
import heapq
heap = []
def preOrder(root):
if not root:
return
heapq.heappush(heap, root.val)
preOrder(root.left)
preOrder(root.right)
preOrder(root)
return heapq.nlargest(k, heap)[-1]
方法二 中序序列
用中序遍历得到中序序列,然后输出倒数第k个元素即可
时间复杂度
O
(
n
)
O(n)
O(n),空间复杂度
O
(
n
)
O(n)
O(n)
def kthLargest2(self, root, k):
"""
中序序列的倒数第k个就是第k大的数
"""
def inOrder(root):
res = []
stack = []
cur = root
while stack or cur:
if cur:
stack.append(cur)
cur = cur.left
else:
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res
inn = inOrder(root)
return inn[-k:][0]
方法三 逆中序遍历记录节点个数
因为要输出第k大的数,而二叉搜索树的中序遍历是递增的,所以应该逆中序遍历。然后在遍历过程中记录下当前的节点个数,到k个就停止遍历。
时间复杂度
O
(
n
)
O(n)
O(n),空间复杂度
O
(
n
)
O(n)
O(n)
def kthLargest3(self, root, k):
"""
逆中序遍历过程中记录节点个数,到第k个为止
时间复杂度 O(N): 当树退化为链表时(全部为右子节点),无论 k 的值大小,递归深度都为 N ,占用 O(N) 时间。
空间复杂度 O(N): 当树退化为链表时(全部为右子节点),系统使用 O(N)大小的栈空间。
"""
self.count = 0 # 记录遍历到的节点的个数
self.ans = 0 # 记录最终答案
def inOrder_reverse(root):
if not root:
return
inOrder_reverse(root.right)
# 遍历到当前节点root,节点个数加1
self.count += 1
if self.count == k:
self.ans = root.val
return
inOrder_reverse(root.left)
inOrder_reverse(root)
return self.ans
783. 二叉搜索树节点最小距离
给定一个二叉搜索树的根节点 root,返回树中任意两节点的差的最小值。
示例:
输入: root = [4,2,6,1,3,null,null]
输出: 1
解释:
注意,root是树节点对象(TreeNode object),而不是数组。
给定的树 [4,2,6,1,3,null,null] 可表示为下图:
4
/ \
2 6
/ \
1 3
最小的差值是 1, 它是节点1和节点2的差值, 也是节点3和节点2的差值。
思路:
相差最小的节点肯定是二叉搜索树中序序列的相邻节点,所以只需要中序遍历时,计算「前驱节点」和「当前节点」的差值并更新即可。
def minDiffInBST(self, root):
"""
:type root: TreeNode
:rtype: int
"""
self.ans = float('inf')
self.pre = float('-inf')
def inOrder(root):
if not root:
return
inOrder(root.left)
if abs(self.pre - root.val) < self.ans:
self.ans = abs(self.pre - root.val)
self.pre = root.val
inOrder(root.right)
inOrder(root)
return self.ans
501. 二叉搜索树中的众数
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
例如:
给定 BST [1,null,2,2],
1
\
2
/
2
返回[2].
提示:如果众数超过1个,不需考虑输出顺序
进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
思路:
方法一 中序序列统计次数
用中序遍历得到中序序列,然后对有序数组进行统计次数,可以使用map,也可以使用快慢指针来统计。这边我们使用快慢指针。
def findMode(self, root):
"""
直接中序序列,有序序列中使用快慢指针统计出现次数最多的数
"""
if not root:
return
def inOrder(root):
res = []
stack = []
cur = root
while stack or cur:
if cur:
stack.append(cur)
cur = cur.left
else:
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res
inn = inOrder(root)
slow, fast = 0, 0
ans = 0 # 最多次数
count = 0 # 当前数出现的次数
arg = [] # 出现最多的数
while fast < len(inn):
if inn[slow] == inn[fast]:
count += 1
fast += 1
else:
if count > ans:
ans = count
arg = []
arg.append(inn[slow])
elif count == ans:
arg.append(inn[slow])
slow = fast
count = 0
if count > ans:
ans = count
arg = []
arg.append(inn[slow])
elif count == ans:
arg.append(inn[slow])
return arg
方法二 不使用额外空间
中序遍历过程中,记录前驱结点的值,和当前节点进行比较,来计算当前数出现的次数。
def findMode2(self, root):
"""
不借助额外空间,在中序遍历过程中使用前驱节点,判断前驱节点是否和当前节点相等来计算个数
:param root:
:return:
"""
self.pre = float('-inf')
self.count = 0 # 实际上代表重复的元素个数-1
self.ans_count = 0
self.arg = []
def inOrder(root):
if not root:
return None
inOrder(root.left)
# 如果节点值等于前驱节点的值,那么重复的个数加1
if self.pre == root.val:
self.count += 1
else: # 否则,count为0
self.count = 0
# 更新ans_count
if self.count > self.ans_count:
self.ans_count = self.count
self.arg = []
self.arg.append(root.val)
elif self.count == self.ans_count:
self.arg.append(root.val)
# 后移
self.pre = root.val
inOrder(root.right)
inOrder(root)
return self.arg
938. 二叉搜索树的范围和
给定二叉搜索树的根结点 root,返回 L 和 R(含)之间的所有结点的值的和。
二叉搜索树保证具有唯一的值。
示例 1:
输入:root = [10,5,15,3,7,null,18], L = 7, R = 15
输出:32
示例 2:
输入:root = [10,5,15,3,7,13,18,1,null,6], L = 6, R = 10
输出:23
思路:
- 如果根节点的值
root.val
处于范围内,那么需要搜索左子树和右子树 - 如果根节点的值
root.val
大于等于R,说明在范围内的只会出现在左子树,所以只需搜索左子树 - 如果根节点的值
root.val
小于等于L,说明在范围内的只会出现在右子树,所以只需搜索右子树
def rangeSumBST(self, root, L, R):
"""
:type root: TreeNode
:type L: int
:type R: int
:rtype: int
"""
if not root:
return 0
if L <= root.val <= R:
return self.rangeSumBST(root.left, L, R) + self.rangeSumBST(root.right, L, R) + root.val
elif root.val < L:
return self.rangeSumBST(root.right, L, R)
elif root.val > R:
return self.rangeSumBST(root.left, L, R)
面试题68 - I. 二叉搜索树的最近公共祖先
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
说明:
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉搜索树中。
思路:
若 root是 p, q的最近公共祖先 ,则只可能为以下情况之一:
- p 和 q在 root 的子树中,且分列 root 的 异侧(即分别在左、右子树中);
p=root
,且 q在 root 的左或右子树中;q=root
,且 p在 root 的左或右子树中;
算法:
- 如果p,q的值都小于root,说明p,q都在root的左子树,就去左子树中递归查找
- 如果p,q的值都大于root,说明p,q都在root的右子树,就去右子树中递归查找
- 如果p,q在root的两侧,那么root就是他们的最小公共祖先,直接返回即可
def lowestCommonAncestor(self, root, p, q):
"""
1. 如果p q在root的异侧,那么root就是他们的最近公共祖先
2. 如果pq都在root的右侧,那么在右子树中递归查找
3. 如果pq都在root的左侧,那么在左子树中递归查找
"""
if not root:
return root
if p.val < root.val and q.val < root.val:
return self.lowestCommonAncestor(root.left, p, q)
if p.val > root.val and q.val > root.val:
return self.lowestCommonAncestor(root.right, p, q)
return root
98. 验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
思路:
方法一递归
一般思路就是按照树递归的结构,每个递归体的作用就是比较根节点和左右孩子节点,看起来应该这么写代码:
def isValidBST2_w(self, root):
if not root: return True
if not root.left and root.val <= root.left:
return False
if not root.right and root.val >= root.right:
return False
return self.isValidBST2_w(root.left) and self.isValidBST2_w(root.right)
但是这种方法只比较了根节点和左右孩子节点的大小,没有比较整棵左右子树和根节点。所以我们需要使用辅助函数,增加函数参数列表,在参数中携带额外信息。
我们设计一个递归函数 helper(root, lower, upper)
来递归判断,函数表示考虑以 root
为根的子树,判断子树中所有节点的值是否都在 (l,r)
的范围内(注意是开区间)。如果 root
节点的值 val
不在 (l,r)
的范围内说明不满足条件直接返回,否则我们要继续递归调用检查它的左右子树是否满足,如果都满足才说明这是一棵二叉搜索树。注意初始值是-inf
和inf
。
def isValidBST2(self, root):
"""
递归,递归函数作用是判断当前树节点范围是否在(min,max)内
"""
def helper(root, min_val, max_val):
"""
判断root这棵树是否在(min_val, max_val)开区间范围内
"""
if not root:
return True
# 1. 判断根节点是否在范围内
if root.val <= min_val or root.val >= max_val:
return False
# 2. 判断左子树,如果左子树不满足,那也不满足
if not helper(root.left, min_val, root.val):
return False
# 3. 判断右子树,如果右子树不满足,那也不满足
if not helper(root.right, root.val, max_val):
return False
return True
return helper(root, float('-inf'), float('inf'))
方法二 中序序列递增
二叉搜索树的左子树小于根节点,右子树大于根节点,所以二叉搜索树的中序序列是递增有序的。那么直接输出中序序列,然后判断是否递增即可。
def isValidBST(self, root):
"""
中序序列 是递增的
"""
def inorder(root):
if not root:
return []
return inorder(root.left) + [root.val] + inorder(root.right)
if not root:
return True
in_res = inorder(root)
for i in range(len(in_res) - 1):
if in_res[i] >= in_res[i+1]:
return False
return True
方法三 递归中序遍历时判断当前节点和前驱节点的大小
中序遍历时,实际上我们可以设置一个全局变量pre
保存遍历时当前节点root
的前驱节点,初始时pre
节点的值是-inf
,然后在遍历过程中不断更新pre=root.val
。
def isValidBST3(self, root):
"""
中序遍历时,判断当前节点root是否大于中序遍历的前一个节点值pre,如果大于,继续遍历,否则直接false
"""
if not root:
return True
# 访问左子树
if not self.isValidBST3(root.left):
return False
# 比较当前节点和前驱节点的大小
if root.val <= self.pre:
return False
self.pre = root.val
# 访问右子树
return self.isValidBST3(root.right)
增删改
701. 二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。
例如,
给定二叉搜索树:
4
/ \
2 7
/ \
1 3
和 插入的值: 5
你可以返回这个二叉搜索树:
4
/ \
2 7
/ \ /
1 3 5
或者这个树也是有效的:
5
/ \
2 7
/ \
1 3
\
4
思路:
递归,判断待插入的节点和根节点的大小关系:
- 若
root == null
,则返回TreeNode(val)
。 - 若
val > root.val
,插入到右子树。 - 若
val < root.val
,插入到左子树。 - 返回
root
。
def insertIntoBST(self, root, val):
"""
:type root: TreeNode
:type val: int
:rtype: TreeNode
"""
if not root:
return TreeNode(val)
if root.val > val:
root.left = self.insertIntoBST(root.left, val)
else:
root.right = self.insertIntoBST(root.right, val)
return root
450. 删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
说明: 要求算法时间复杂度为 O(h),h 为树的高度。
root = [5,3,6,2,4,null,7]
key = 3
5
/ \
3 6
/ \ \
2 4 7
给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
5
/ \
4 6
/ \
2 7
另一个正确答案是 [5,2,6,null,4,null,7]。
5
/ \
2 6
\ \
4 7
思路:
当我们找到待删除的节点后,我们需要将这个节点删除,并且使得以这个节点为根节点的树还是一颗二叉搜索树。那么如何将以这个节点为根节点的树修改成二叉搜索树就是这道题的重点。我们需要对待删除的节点分情况讨论:
- 待删除的节点是叶子节点,可以直接删除——
root=None
- 待删除的节点有左孩子,无右孩子:那么该节点可以被左子树中最大的节点替代(中序序列中该节点的前一个节点,称之为前驱结点)。这种替代方式是最省事的,因为只需要交换这两个节点的值即可,其他节点的结构和值都不需要改动。
- 待删除的节点有右孩子,无左孩子:那么该节点可以被右子树中最小的节点替代(中序序列中该节点的后一个节点,称之为后继结点)。
- 待删除的节点有右孩子,也有左孩子:那么该节点同样可以被右子树中最小的节点替代
算法:
- 如果
key > root.val
,说明要删除的节点在右子树,root.right = deleteNode(root.right, key)
。 - 如果
key < root.val
,说明要删除的节点在左子树,root.left = deleteNode(root.left, key)
。 - 如果
key == root.val
,则该节点就是我们要删除的节点,则: - 如果该节点是叶子节点,则直接删除它:
root = null
。 - 如果该节点不是叶子节点且有右节点,则用它的后继节点的值替代
root.val = successor.val
,然后删除后继节点。 - 如果该节点不是叶子节点且只有左节点,则用它的前驱节点的值替代
root.val = predecessor.val
,然后删除前驱节点。 - 返回
root
。
至于「前驱节点」和「后继节点」如何得到,我们可以先取该节点的左/右孩子节点,然后一直取该节点的右/左节点,直到为空。注意,「前驱结点」和「后继节点」会常出现在面试中。
class Solution(object):
def Successor(self, root):
"""
Successor 代表的是中序遍历序列的下一个节点。即比当前节点大的最小节点,简称后继节点。
先取当前节点的右节点,然后一直取该节点的左节点,直到左节点为空,则最后指向的节点为后继节点。
"""
root = root.right
while root.left:
root = root.left
return root.val
def Predecessor(self, root):
"""
Predecessor 代表的是中序遍历序列的前一个节点。即比当前节点小的最大节点,简称前驱节点。
先取当前节点的左节点,然后取该节点的右节点,直到右节点为空,则最后指向的节点为前驱节点。
"""
root = root.left
while root.right:
root = root.right
return root.val
def deleteNode(self, root, key):
"""
:type root: TreeNode
:type key: int
:rtype: TreeNode
"""
if not root:
return None
# 1. 去右子树中删除
if key > root.val:
root.right = self.deleteNode(root.right, key)
# 2. 去左子树中删除
elif key < root.val:
root.left = self.deleteNode(root.left, key)
else: # 根节点待删除,那么需要分成三类
# 1. 根节点是叶子节点
if not root.left and not root.right:
root = None
# 2. 根节点有右孩子,找到后继节点替换根节点
elif root.right:
root.val = self.Successor(root)
root.right = self.deleteNode(root.right, root.val)
# 3. 根节点没有右孩子,只有左孩子,找到前驱节点替换根节点
else:
root.val = self.Predecessor(root)
root.left = self.deleteNode(root.left, root.val)
return root
669. 修剪二叉搜索树
给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。
示例 1:
输入:
1
/ \
0 2
L = 1
R = 2
输出:
1
\
2
示例 2:
输入:
3
/ \
0 4
\
2
/
1
L = 1
R = 3
输出:
3
/
2
/
1
思路:
这道题需要揣摩一下递归思路
- 如果根节点的值 r o o t . v a l > R root.val>R root.val>R,说明需要抛弃根节点及其右子树,返回修剪后的左子树
- 如果根节点的值 r o o t . v a l < L root.val<L root.val<L,说明需要抛弃根节点及其左子树,返回修剪后的右子树
- 如果根节点的值在范围内,那么递归地去修剪左子树和右子树,最后返回根节点。
def trimBST(self, root, L, R):
"""
:type root: TreeNode
:type L: int
:type R: int
:rtype: TreeNode
"""
# 1. 抛弃右子树及根节点,返回修剪后的左子树
if root.val > R:
return self.trimBST(root.left, L, R)
# 2. 抛弃左子树及根节点,返回修剪后的右子树
if root.val < L:
return self.trimBST(root.right, L, R)
root.left = self.trimBST(root.left, L, R)
root.right = self.trimBST(root.right, L, R)
return root
建树
538. 把二叉搜索树转换为累加树
给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。
例如:
输入: 原始二叉搜索树:
5
/ \
2 13
输出: 转换为累加树:
18
/ \
20 13
思路:
对于例子中简单结构的二叉树,我们的遍历顺序是右中左,在遍历过程中记录下累加和,然后修改当前节点的值为节点值加上累加和。
所以我们需要一个全局变量total
记录遍历过程中的和,先递归转换右子树,然后修改当前根节点,接着递归转换左子树。
class Solution(object):
def __init__(self):
self.total = 0
def convertBST(self, root):
"""
:type root: TreeNode
:rtype: TreeNode
"""
if not root:
return None
# 先转换右子树,过程中会更新累加和
self.convertBST(root.right)
# 修改根节点的值
self.total += root.val
root.val = self.total
# 最后转换左子树
self.convertBST(root.left)
return root
剑指 Offer 36. 二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
思路:
题目简单来说,就是将二叉搜索树转换成一个有序的双向循环链表,不创建新的节点,即直接修改树节点的左右孩子指向。
因为是二叉搜索树,而需要生成有序的链表,所以考虑到使用中序遍历,在递归中序遍历的过程中修改「前驱节点」和「当前节点」的指向。中序遍历过程中的「前驱节点」可以借鉴验证二叉搜索树
的第三种方法,用一个全局变量保存,在遍历过程中更新。
除了「前驱节点」这个全局变量之外,我们还需要一个「链表头结点」的全局变量,初始都设为None
。这两个节点在中序遍历结束之后分别代表双向链表的尾头指针,递归遍历结束后修改头尾节点的指向即可。
我们的「中序遍历递归函数」的作用是中序遍历root
这棵树,在过程中修改指向。这里要注意需要对前驱指针pre
进行一个判断:当pre=None
时,说明是初始状态,此时保存该节点为头结点;当pre
不为空时,可以修改前驱结点和当前节点的指向,使其双向连接。
def treeToDoublyList(self, root):
"""
要生成有序的双向链表,那么就想到中序遍历时修改节点的指向
:type root: Node
:rtype: Node
"""
if not root:
return None
self.pre = None # 中序遍历时当前节点root的前驱结点
self.head = None # 第一个非空节点,即双向链表的头结点,需要保存
def inorder(root):
"""
中序遍历root,并修改指向,生成双向链表
"""
if not root:
return
inorder(root.left)
# 如果pre不空的话,将pre和root双向连接
if self.pre:
self.pre.right = root
root.left = self.pre
# 如果pre为空,说明是初始状态,将当前节点作为头结点记录下来
else:
self.head = root
# 后移pre
self.pre = root
inorder(root.right)
inorder(root)
# 中序遍历完成时,head pre分别是双向链表的头尾节点
self.head.left = self.pre
self.pre.right = self.head
return self.head
1382. 将二叉搜索树变平衡
给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。
如果一棵二叉搜索树中,每个节点的两棵子树高度差不超过 1 ,我们就称这棵二叉搜索树是 平衡的 。
如果有多种构造方法,请你返回任意一种。
输入:root = [1,null,2,null,3,null,4,null,null]
输出:[2,1,3,null,null,null,4]
解释:这不是唯一的正确答案,[3,1,4,null,2,null,null] 也是一个可行的构造方案。
思路:
「平衡」要求它是一棵空树或它的左右两个子树的高度差的绝对值不超过 1,这很容易让我们产生这样的想法——左右子树的大小越「平均」,这棵树会不会越平衡?于是一种贪心策略的雏形就形成了:我们可以通过中序遍历将原来的二叉搜索树转化为一个有序序列,然后对这个有序序列二分递归建树。
def balanceBST(self, root):
"""
:type root: TreeNode
:rtype: TreeNode
"""
def inOrder(root):
if not root:
return []
return inOrder(root.left) + [root.val] + inOrder(root.right)
inn = inOrder(root)
def create(nums):
# 递归出口,数组为空,返回空节点
if len(nums) == 0:
return None
mid = len(nums) // 2
root = TreeNode(nums[mid])
root.left = create(nums[:mid])
root.right = create(nums[mid+1:])
return root
return create(inn)
109. 有序链表转换二叉搜索树
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
给定的有序链表: [-10, -3, 0, 5, 9],
一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
思路:
根据二叉搜索树的性质,我们只要把链表的中点作为根节点,然后递归地去根据前半部分链表构建左子树,根据后半部分链表构建右子树即可,这样二分的话最后构建的树肯定是高度平衡的。
所以这道题涉及到的知识点就是找链表的中点,典型的方法是「双指针法」,快慢指针分别走两步和一步,当快指针走到链表末尾时,此时慢指针就指向链表的中点。我们还需要一个前驱指针作为慢指针的前驱,因为我们需要将链表分割成前后两部分,分别为head
和slow
。
def sortedListToBST(self, head):
"""
将链表中点二叉搜索树的根节点,然后递归左右两部分链表即可
:type head: ListNode
:rtype: TreeNode
"""
def findMid(head):
"""
用快慢指针寻找链表的中点,并且截断,返回后半部分链表
"""
dummyhead = ListNode(0)
dummyhead.next = head
prev, slow, fast = dummyhead, head, head
while fast and fast.next:
prev = prev.next
slow = slow.next
fast = fast.next.next
# 截断
prev.next = None
return slow
if not head:
return None
# 当链表只有一个节点时候,直接返回这个节点即可
if head.next is None:
return TreeNode(head.val)
mid = findMid(head)
root = TreeNode(mid.val)
root.left = self.sortedListToBST(head)
root.right = self.sortedListToBST(mid.next)
return root
96. 不同的二叉搜索树
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
思路:
我们可以遍历每个数字i
,将该数字作为树根,1 ... (i-1)
序列将成为左子树,(i+1) ... n
序列将成为右子树。于是,我们可以递归地从子序列构建子树。由于根各自不同,每棵二叉树都可以保证是独特的。此外,我们需要注意到,(i+1) ... n
序列的二叉搜索树个数和1...(n-i)
序列是一样多的,也就是说只和节点的个数有关。所以,对于i
作为根节点,1 ... (i-1)
序列成为左子树有numTrees(i-1)
种,(i+1) ... n
序列成为右子树有numTrees(n-i)
种,最后相乘得到i
作为根节点的二叉搜索树个数。因此,这就是一个动态规划问题。
我们可以使用记忆化搜索的方法,将n
个节点对应的树个数保存到字典中,来减少复杂度。
class Solution:
map = dict() # <n, kinds> n个不同节点有kinds种
def numTrees(self, n):
if n in self.map:
return self.map.get(n)
if n <= 1: # 0或1个节点,都是1种
return 1
res = 0
for i in range(1, n+1): # 以当前节点i为根节点,分割左右两部分
res += self.numTrees(i-1) * self.numTrees(n-i) # 左边有i-1个节点,右边有n-i个节点
self.map[n] = res
return res
95. 不同的二叉搜索树 II
给定一个整数 n,生成所有由 1 … n 为节点所组成的 二叉搜索树 。
示例:
输入:3
输出:
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
思路:
假如左侧和右侧的树分别求好了,我们只要运用组合的原理,将左右两者进行做和就好了,我们需要两层循环来完成这个过程。
def generateTrees(self, n):
"""
:param n:
:return:
"""
def generateTree(start, end):
"""
生成[start,end]为节点值的树
"""
if start > end:
return [None]
res = []
for i in range(start, end+1):
ls = generateTree(start, i-1) # 分别生成左右子树
rs = generateTree(i+1, end)
for l in ls:
for r in rs:
node = TreeNode(i) # 以该点i作为根节点,连接左右子树
node.left = l
node.right = r
res.append(node)
return res
return generateTree(1, n)