持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情
大家好呀,我是帅蛋。
在二叉搜索树前几篇实战文章中,我们应用过二叉搜索树的一个性质来解题:对二叉搜索树进行中序遍历时,得到的结果是一个有序的序列。
一直把人二叉搜索树整成有序序列,今天我们就反过来,将有序的序列整成一棵二叉搜索树。
LeetCode 108:将有序数组转换为二叉搜索树
题意
给定一个升序数组 nums,将其转换为一棵高度平衡的二叉搜索树。
高度平衡二叉树是一棵满足【每个节点的左右两个子树的高度差的绝对值不超过1】的二叉树。
题意
输入:nums = [-10, -3, 0, 5, 9] 输出:[0, -3, 9, -10, null, 5] or [0, 10, 5, null, -3, null, 9]
提示
- 1 <= nums.length <= 10^4
- -10^4 <= nums[i] <= 10^4
- nums 按严格递增顺序排列
题目解析
将有序数组转换为二叉搜索树,难度简单,说白了就是构造一棵二叉搜索树。
之前关于构造二叉树这种事,我们都做了好几次了,可以说都是老熟人儿:
ACM 选手图解 LeetCode 从前序与中序遍历构造二叉树
ACM 选手图解 LeetCode 从中序与后序遍历构造二叉树
构造二叉树这种事,说白了就是根据情况,找到一个点当根节点,然后左区间是一棵子树,右区间是一棵子树。
比如就拿中序遍历来说,它的遍历顺序为左子树、根、右子树,它就是下面那样:
我在开头的时候讲过:对二叉搜索树进行中序遍历时,得到的结果是一个有序的序列。
那把一个有序数组构造成二叉搜索树,其实这个“有序数组”就是可以看成是二叉搜索树中序遍历出来的。
这样的话,那构造二叉搜索树就太简单了:
- 有序数组中间节点为根节点。
- 根节点左侧区间为左子树。
- 根节点右侧区间为右子树。
这里有一点大家稍微注意一下,那就是找【中间节点】。
如果数组是奇数那没问题,中间那个就是根节点,如果数组是偶数个,那中间节点就是两个,可能你有疑问,这个时候该怎么取?
其实很简单,取左边那个或者右边那个都可以,比如对于 nums = [1,3],取 1 就是右边那棵,取 3 就是左边那棵。
递归法
实现【递归算法】,两步走起来:
- 找出重复的子问题(递推公式)。
- 终止条件。
(1) 找出重复的子问题。 我在题目解析中讲过了,有序序列构造二叉树:
- 有序数组中间节点为根节点。
- 根节点左侧区间为左子树。
- 根节点右侧区间为右子树。
那重复的子问题就是,找到根节点,递归构造左子树,递归构造右子树。
mid = left + (right - left) // 2
# 根节点
midNode = TreeNode(nums[mid])
# 递归构造左子树
midNode.left = self.process(nums, left, mid - 1)
# 递归构造右子树
midNode.right = self.process(nums, mid + 1, right)
(2) 确定终止条件。
对于终止条件来说,当 left > right 时,就终止,返回 None,因为这个时候就是空节点。
if left > right:
return None
接下俩还是走个过场:这两点确定好了,代码基本也就写出来了。
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 process(self, nums, left, right):
if left > right:
return None
# 找数组中间元素
mid = left + (right - left) // 2
# 根节点
midNode = TreeNode(nums[mid])
# 递归构造左子树
midNode.left = self.process(nums, left, mid - 1)
# 递归构造右子树
midNode.right = self.process(nums, mid + 1, right)
return midNode
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
return self.process(nums, 0, len(nums) - 1)
Java 代码实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode process(int[] nums, int left, int right){
if(left > right){
return null;
}
// 找数组中间元素
int mid = left + ((right - left) >> 1);
// 根节点
TreeNode midNode = new TreeNode(nums[mid]);
// 递归构造左子树
midNode.left = process(nums, left, mid - 1);
// 递归构造右子树
midNode.right = process(nums, mid + 1, right);
return midNode;
}
public TreeNode sortedArrayToBST(int[] nums) {
TreeNode root = process(nums, 0, nums.length - 1);
return root;
}
}
本题解对于每个数组元素都要访问到,所以时间复杂度为 O(n) 。
在递归过程中调用了额外的栈空间,栈的大小取决于二叉搜索树的高度,所以空间复杂度为 O(logn) 。
非递归法(迭代)
基于二叉搜索树的特性,对于二叉搜索树的非递归法,就是一个模拟的过程。
这里为了模拟构造二叉搜索树的过程,需要用到 3 个队列:
- rootQue 存放遍历的节点。
- leftQue 存放左区间的下标。
- rightQue 存放右区间的下标。
之后就是不断的模拟寻找根节点,构造左子树和构造右子树。
我们以 nums = [-10, -3, 0, 5, 9] 为例。
首先初始化二叉搜索树的根节点以及 3 个队列:
# 初始化根节点
root = TreeNode(0)
# 队列存放遍历的节点
rootQue = [root]
# 队列存放左区间下标
leftQue = [0]
# 队列存放右区间下标
rightQue = [len(nums) - 1]
第 1 步,rootQue 不为空,cur 记录 rootQur 出队列节点 0,left 记录 leftQue 出队列下标 0,right 记录 rightQue 出队列下标 4。
cur = rootQue.pop(0)
left = leftQue.pop(0)
right = rightQue.pop(0)
此时的中间下标 mid = 2, nums[2] = 0,赋值给 cur,即此时 cur.val = nums[2] = 0。
# 找数组中间元素
mid = left + (right - left) // 2
# 将中间元素值赋值给节点
cur.val = nums[mid]
接下来处理左区间,此时 left = 0,mid = 2,left < mid,初始化 cur 节点的左孩子节点,将左孩子 cur.left、左区间的左下标 0 和左区间的右下标 mid - 1 = 1 分别入队列。
if left < mid:
cur.left = TreeNode(0)
rootQue.append(cur.left)
leftQue.append(left)
rightQue.append(mid - 1)
下面处理右区间,同理初始化 cur 节点的右孩子节点,将右孩子 cur.right、右区间的左下标 mid + 1 = 3 和右区间的右下标 4 分别入队列。
if right > mid:
cur.right = TreeNode(0)
rootQue.append(cur.right)
leftQue.append(mid + 1)
rightQue.append(right)
第 2 步,rootQue 不为空,cur 记录 rootQur 出队列节点 0,left 记录 leftQue 出队列下标 0,right 记录 rightQue 出队列下标 1。
此时的中间下标 mid = 0, nums[0] = -10,赋值给 cur,即此时 cur.val = nums[0] = -10。
因为篇幅原因,在这我就不把全部的都画完了。
之后的步骤都是和第 1 步的一样,不断的初始化节点、入队列出队列,直至 rootQue 为空。
大家可以自己动手把剩下的步骤画出来,检验一下自己是不是真的搞懂了。
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 sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
if len(nums) == 0:
return None
# 初始化根节点
root = TreeNode(0)
# 队列存放遍历的节点
rootQue = [root]
# 队列存放左区间下标
leftQue = [0]
# 队列存放右区间下标
rightQue = [len(nums) - 1]
while rootQue:
cur = rootQue.pop(0)
left = leftQue.pop(0)
right = rightQue.pop(0)
# 找数组中间元素
mid = left + (right - left) // 2
# 将中间元素值赋值给节点
cur.val = nums[mid]
# 处理左区间
if left < mid:
cur.left = TreeNode(0)
rootQue.append(cur.left)
leftQue.append(left)
rightQue.append(mid - 1)
# 处理右区间
if right > mid:
cur.right = TreeNode(0)
rootQue.append(cur.right)
leftQue.append(mid + 1)
rightQue.append(right)
return root
Java 代码实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
if(nums.length == 0){
return null;
}
// 初始化根节点
TreeNode root = new TreeNode(0);
// 队列存放遍历的节点
Queue<TreeNode> rootQue = new LinkedList<>();
// 队列存放左区间下标
Queue<Integer> leftQue = new LinkedList<>();
// 队列存放右区间下标
Queue<Integer> rightQue = new LinkedList<>();
// 初始化 3 个队列
rootQue.offer(root);
leftQue.offer(0);
rightQue.offer(nums.length - 1);
while (!rootQue.isEmpty()){
TreeNode cur = rootQue.poll();
int left = leftQue.poll();
int right = rightQue.poll();
// 找数组中间元素
int mid = left + ((right - left) >> 1);
// 将中间元素值赋值给节点
cur.val = nums[mid];
// 处理左区间
if (left < mid) {
cur.left = new TreeNode(0);
rootQue.offer(cur.left);
leftQue.offer(left);
rightQue.offer(mid - 1);
}
// 处理右区间
if (right > mid) {
cur.right = new TreeNode(0);
rootQue.offer(cur.right);
leftQue.offer(mid + 1);
rightQue.offer(right);
}
}
return root;
}
}
同样对于非递归法,其时间复杂度为 O(n) ,但是额外申请了 3 个队列空间,所以非递归法空间复杂度为 O(n) 。
图解将有序数组转化为二叉搜索树到这就结束辣,是不是成就感满满!
做完这道题我们就厉害了,不管是将二叉搜索树搞成有序序列,还是将有序序列构造成二叉搜索树我们都玩了个遍!
还是要记得做好总结,别做完了就觉得是会了!
当然也不要忘了我的点赞,让我看到你呀!
我是蛋蛋,我们下次见啦!