将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
递归
思路:
- 给定有序数组(升序),联想二叉搜索树的中序遍历为一个升序数组——题目给的升序数组即为二叉搜索树的中序遍历
- 平衡二叉树:把根结点选为数组的中点即可
- 根据根结点将数组一分为二,递归即可
- 注意:包括左边界,不包括右边界
public TreeNode sortedArrayToBST(int[] nums) {
return sortedArrayToBST(nums,0,nums.length);
}
private TreeNode sortedArrayToBST(int[] nums,int start,int end) {
if(start==end)
return null;
int mid=(start+end)>>1;
TreeNode root=new TreeNode(nums[mid]);
root.left=sortedArrayToBST(nums,start,mid);
root.right=sortedArrayToBST(nums,mid+1,end);
return root;
}
时间复杂度:O(N),遍历每个值,确定其节点位置及左右子树
空间复杂度:O(1),函数递归调用,构建二叉搜索树转化为构建左右子二叉搜索树(本质一致)
栈(后进先出的特点)——深度优先
- 一部分递归问题可转换成动态规划问题,实现空间换时间,自顶向下再向顶-->自底向上
- 一部分递归问题仅可以用栈去莫烦递归的过程,对时间空间复杂度无好处,如本题
思路:递归实质是一个压栈出栈的过程
- 压栈:开始对数组中该数字进行左子树的创建
- 弹栈:该树左子树建立完毕,开始创建其右子树
- 循环条件:数组中还有未被遍历的节点或存储二叉搜索树子节点的栈不为空
- 将数组中的数一分为二,得到根结点及其左右子树的中序遍历
- 将左右子树的中序遍历压栈
- 弹栈进行一分为左右子树的处理
我们先定义一个栈,存储递归的参数:函数的start,end以及内部定义的root
class MyTreeNode{
TreeNode root;
int start;
int end;
MyTreeNode(TreeNode r,int s,int e){
this.root=r;
this.start=s;
this.end=e;
}
}
利用栈递归实现二插平衡树的创建
public TreeNode sortedArrayToBST1(int[] nums) {
if(nums.length==0)
return null;
//存储根结点
Stack<MyTreeNode> rootStack=new Stack<>();
int start=0;
int end=nums.length;
int mid=(start+end)>>1;
TreeNode root=new TreeNode(nums[mid]);
TreeNode curRoot=root;
rootStack.push(new MyTreeNode(root,start,end));
while (end-start>1 || !rootStack.isEmpty()) {
//生成左子树
while(end-start>1) {
mid=(start+end)>>1;//当前根结点
end=mid;//左子树的结尾
mid=(start+end)>>1;//左子树的中点
curRoot.left=new TreeNode(nums[mid]);
curRoot=curRoot.left;
rootStack.push(new MyTreeNode(curRoot,start,end));
}
//生成右子树
MyTreeNode myNode=rootStack.pop();
start=myNode.start;
end=myNode.end;
mid=(start+end)>>1;
start=mid+1;//右子树的start
curRoot=myNode.root;//当前根结点
if(start<end) {//判断当前范围是否有数
mid=(start+end)>>1;
curRoot.right=new TreeNode(nums[mid]);
curRoot=curRoot.right;
rootStack.push(new MyTreeNode(curRoot,start,end));
}
}
return root;
}
队列(先进先出的特点)——广度优先
思路:
- 将t层节点入队
- t层节点一个个出队并赋值
- 将t层每个出队节点的左右子树起始范围入队,即t+1层
先生成子树left和right,但不进行赋值,仅把子树的范围传入队,出队时赋值,故无需单独考虑根结点
public TreeNode sortedArrayToBST2(int[] nums) {
if(nums.length==0)
return null;
Queue<MyTreeNode> rootQueue=new LinkedList<>();
TreeNode root=new TreeNode(0);
rootQueue.offer(new MyTreeNode(root,0,nums.length));
while(!rootQueue.isEmpty()) {
MyTreeNode myRoot=rootQueue.poll();
int start=myRoot.start;
int end=myRoot.end;
int mid=(start+end)>>1;
TreeNode curRoot=myRoot.root;
curRoot.val=nums[mid];
//System.out.println("出队"+curRoot.val+" "+start+" "+end);
//左子树构建
if(start<mid) {
curRoot.left=new TreeNode(0);//左子树根结点
rootQueue.offer(new MyTreeNode(curRoot.left,start,mid));
}
//右子树构建
if(mid+1<end) {
curRoot.right=new TreeNode(0);//右子树根结点
rootQueue.offer(new MyTreeNode(curRoot.right,mid+1,end));
}
}
return root;
}