1、有序数组转换为二叉搜索树(108)
题目描述:
【简单题】
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
思路分析
- 元素是有序的,要生成一棵BST,且要求BST是平衡的。也就是说对于二叉树的每个结点,要让其左子树与右子树的结点数量尽可能相等。
- 二叉搜索树的中序遍历即为有序序列,中序遍历的顺序为:左子树 → \to →根节点 → \to → 右子树。
- 因此,我们可以选择数组的中间点作为根结点。
- 如果我们选定数组的索引为i的元素为根结点,那么左子树与右子树结点的值的区间就唯一确定了(因为数组有序)。左子树结点值的区间[0, i - 1],右子树结点值的区间[i + 1, nums.length - 1]。要让BST平衡,那么所选的i就应该选数组的中间元素。
- 平衡BST的左子树,右子树也均是平衡BST,所以也需要用同样的策略去选值。于是可以将区间[0, num.length - 1]上的问题转化为两个子问题[0, mid]与[mid + 1, num.length - 1]上构建平衡二叉树。这就是递归的结构。
【代码实现】
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def sortedArrayToBST(self, nums):
"""
:type nums: List[int]
:rtype: TreeNode
"""
def helper(left,right):
if left>right:
return None
mid_index=(left+right)//2
root=TreeNode(nums[mid_index])#根结点
root.left=helper(left,mid_index-1)
root.right=helper(mid_index+1,right)
return root
return helper(0,len(nums)-1)
-
时间复杂度: O ( n ) O(n) O(n),其中n 是数组的长度。每个数字只访问一次。
-
空间复杂度: O ( log n ) O(\log n) O(logn),其中n 是数组的长度。空间复杂度不考虑返回值,因此空间复杂度主要取决于递归栈的深度,递归栈的深度是 O ( log n ) O(\log n) O(logn)。
2、二叉搜索树的最近公共祖先(235)
题目描述:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
【名词解释】
最近公共祖先:
“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
题解一——递归
思路分析
1、从根结点遍历树;
2、如果指定节点p,q都在左子树上,那么最近公共祖先在左子树上,则以左孩子为根结点调用函数;
3、如果指定节点p,q都在右子树上,那么最近公共祖先在右子树上,则以右孩子为根结点调用函数;
4、如果指定节点p,q一个在右子树上,一个在左子树上,则最近公共祖先为根结点。
如何判定节点在根结点的左子树还是在右子树上?
二叉搜索树的性质就派上用场了:
- 节点左子树上所有节点的值都小于节点的值
- 节点右子树上所有节点的值都大于节点的值
- 左子树与右子树都是二叉搜索树
【代码实现】
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def lowestCommonAncestor(self, root, p, q):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
if p.val<root.val and q.val<root.val:
return self.lowestCommonAncestor(root.left,p,q)
elif p.val>root.val and q.val>root.val:
return self.lowestCommonAncestor(root.right,p,q)
else:
return root
-
时间复杂度: O ( N ) O(N) O(N)
其中 N 为 BST 中节点的个数,在最坏的情况下我们可能需要访问 BST 中所有的节点。 -
空间复杂度: O ( N ) O(N) O(N)
所需开辟的额外空间主要是递归栈产生的,之所以是 N N N 是因为 BST 的高度为 N N N。
题解二——迭代
关键是找分割节点,分割点就是能让节点 p 和节点 q 不能在同一颗子树上的那个节点,或者是节点 p 和节点 q 中的一个,这种情况下其中一个节点是另一个节点的父亲节点。
当根结点不为空时,执行以下操作:
- 1、如果指定节点p,q都在左子树上,那么根结点更新为其左孩子
- 2、如果指定节点p,q都在右子树上,那么根结点更新为其右孩子
- 3、否则,返回根节点
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def lowestCommonAncestor(self, root, p, q):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
while root:
if p.val<root.val and q.val<root.val:
root=root.left
elif p.val>root.val and q.val>root.val:
root=root.right
else:
return root
-
时间复杂度: O ( N ) O(N) O(N)
其中 N 为 BST 中节点的个数,在最坏的情况下我们可能需要访问 BST 中所有的节点。 -
空间复杂度: O ( 1 ) O(1) O(1)
3、二叉搜索树中的众数(501)
题目描述:
【简单题】
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
思路分析
1、众数:出现最多次数的数
2、二叉搜索树性质:中序遍历为递增序列
3、由二叉搜索树中序遍历性质得两邻节点值相等的可能性:当前结点与前一节点相等。在统计元素出现次数中,如同双指针的做法,这里也需要记录上一个结点TreeNode pre;这样才能知道当前结点值与谁比较;另外还需要记录某个值的出现次数counter,以及出现次数的最大值max_counter(否则你咋知道谁出现最多次)。并且这里遍历过程中的众数信息需要记录(List存放众数)及更新。
4、建立中序遍历辅助函数:一边遍历,一边比较数值,得到众数表
- 特殊情况:如果根结点为空,返回空
- 中序遍历左子树
统计次数,比较,更新众数表
- 如果当前结点值与上一个结点值相等,那么这个数字的出现次数+1。否则,清零。
- 更新上一节点pre为当前结点值
- 如果上一个数字出现次数counter最大,需要更新众数信息。首先更新最大出现次数max_count = counter;然后将之前记录的众数清空,再将上一个数字放入众数表中
- 如果一个数字出现次数等于最大出现次数,那么目前来看,它也是可能的众数,加入列;
- 否则,上一个数字一定不是众数,不管它,继续保留List中的数字。
- 中序遍历右子树
上述中需要初始化当前次数,最大次数,众数表
【代码实现】
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def __init__(self):
self.max_counter = 0 #初始化最大次数为0
self.pre = float("-inf") #设置前一节点值为负无穷
self.counter = 0 #初始化当前结点次数为0
def findMode(self, root):
res = [] #存储众数
def inorder(root): # 中序遍历,遍历过程中比较数值
if not root:
return None
inorder(root.left)
if root.val == self.pre: #如果相等counter加1,不等counter清0
self.counter += 1
else:
self.counter = 0
self.pre = root.val #更新前一结点为当前结点
if self.counter > self.max_counter: #与最多的比较,大于则清空列表,重新添加,相等则继续添加,小于则保持
self.max_counter = self.counter
res.clear()
res.append(root.val)
elif self.counter == self.max_counter:
res.append(root.val)
inorder(root.right)
return res
return inorder(root)
-
时间复杂度: O ( N ) O(N) O(N)
其中 N 为 BST 中节点的个数,在最坏的情况下我们可能需要访问 BST 中所有的节点。 -
空间复杂度: O ( 1 ) O(1) O(1)
4、修剪二叉搜索树(669)
题目描述:
【简单题】
给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。
思路分析
\quad \quad 题目要求所有节点的值在在[L, R]中 (R>=L),考虑二叉搜索树的性质:
- 节点左子树上所有节点的值都小于节点的值
- 节点右子树上所有节点的值都大于节点的值
- 左子树与右子树都是二叉搜索树
\quad \quad 结合性质以及题目要求,可产生以下思路求解此题:
1、修剪一棵树,如果根结点的值小于给定的左边界L,那么当前结点及其左子树就会被修剪掉,修剪后的树应该是其右子树,但是右子树不一定是符合范围的树,所以要对其右子树叶进行修剪,然后返回修剪后的右子树。
2、同理,根结点的值大于给定的右边界R,修剪后的树应该是其左子树且要对左子树修剪。
3、涉及到改变树的结构,就需要更新链接,如果当前结点值在范围内,那么修建其左右子树,并且更新左右链接。最后将当前修剪好的子树返回。
【python代码实现】
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def trimBST(self, root, L, R):
"""
:type root: TreeNode
:type L: int
:type R: int
:rtype: TreeNode
"""
if not root:
return
elif root.val<L:
return self.trimBST(root.right,L,R)
elif root.val>R:
return self.trimBST(root.left,L,R)
else:
root.left=self.trimBST(root.left,L,R)
root.right=self.trimBST(root.right,L,R)
return root
-
时间复杂度: O ( N ) O(N) O(N)
其中 N 为 BST 中节点的个数,在最坏的情况下我们可能需要访问 BST 中所有的节点。 -
空间复杂度: O ( N ) O(N) O(N),即使我们没有明确使用任何额外的内存,在最糟糕的情况下,我们递归调用的栈可能与节点数一样大。