算法虽难,循序渐进,督促自己,总有进步;
本博文仅为了督促自己学习算法,如有遗漏或错误之处,请不吝指教;
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例 :
给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
题目中给出了两个条件:一个升序排序的有序数组,转换为一棵高度平衡二叉搜索树
我们先了解下,什么是二叉搜索树:
二叉搜索树
二叉搜索树(Binary Search Tree)是指一棵空树或具有如下性质的二叉树:
基于以上性质,我们可以得出一个二叉搜索树的特性:二叉搜索树的中序遍历结果为递增序列
中序遍历的顺序为:左节点 → 根节点 → 右节点
遍历树的方法有哪些:
DFS(深度优先),又细分为先序遍历,中序遍历,后序遍历
BFS(广度优先)
下图中按照不同的方法遍历对应子树,得到的遍历顺序都是 1-2-3-4-5。根据不同子树结构比较不同遍历方法的特点
将有序数组转换为二叉搜索树的结果为什么 不唯一 ?
二叉搜索树的中序遍历是一个升序序列。
将有序数组作为输入,可以把该问题看做 根据中序遍历序列创建二叉搜索树。
这个问题的答案唯一吗。例如:是否可以根据中序遍历序列和二叉搜索树之间是否一一对应,答案是 否定的
下面是一些关于 BST 的知识。
因此,“有序数组 -> BST”有多种答案。
因此,添加一个附件条件:树的高度应该是平衡的、例如:每个节点的两棵子树高度差不超过 1
这种情况下答案唯一吗?仍然没有。
高度平衡意味着每次必须选择中间数字作为根节点。这对于奇数个数的数组是有用的,但对偶数个数的数组没有预定义的选择方案。
对于偶数个数的数组,要么选择中间位置左边的元素作为根节点,要么选择中间位置右边的元素作为根节点,不同的选择方案会创建不同的平衡二叉搜索树。
方法一:始终选择中间位置左边的元素作为根节点,
方法二:始终选择中间位置右边的元素作为根节点。
方法一和二会生成不同的二叉搜索树,这两种答案都是正确的。
如何构造树
构造一棵树的过程可以拆分成无数个这样的子问题:构造树的每个节点以及节点之间的关系。对于每个节点来说,都需要:
因题目要求构造一棵「高度平衡」的树,所以我们在选取节点时选择数组的中点作为根节点,以此来保证平衡性。
以题目给出的 [-10,-3,0,5,9] 为例。
我们选取数组中点,即数字 0 作为根节点。此时,以 0 为分界点将数组分为左右两个部分,左侧为 [-10, -3],右侧为 [5, 9]。
因该数组为升序排列的有序数组,所以左侧数组值均小于 0,可作为节点 0 的左子树;右侧数组值均大于 0,可作为节点 0 的右子树。
同上述步骤,将 [-10, -3] 和 [5, 9] 单独看作两棵树,从而继续为他们构造左右子树。
对于左侧数组 [-10, -3],我们选取 -3 作为根节点;对于右侧数组 [5, 9],选取 9 作为根节点。
最终构造结果如下:
递归设计
函数作用
通过上述解题过程我们可以明确该问题的子问题是:构造树的每个节点以及该节点的左右子树。因此,递归函数的作用很明显:
何时结束
当输入的递增数组为空时,只能构成一棵空树,此时返回空节点。
何时调用
当构造节点的左右子树时,对递增数组进行拆分并进行递归调用。
class Solution {
int[] nums;
public TreeNode helper(int left, int right) {
if (left > right) return null;
// always choose left middle node as a root
int p = (left + right) / 2;
// inorder traversal: left -> node -> right
TreeNode root = new TreeNode(nums[p]);
root.left = helper(left, p - 1);
root.right = helper(p + 1, right);
return root;
}
public TreeNode sortedArrayToBST(int[] nums) {
this.nums = nums;
return helper(0, nums.length - 1);
}
}
始终选择中间位置右边元素作为根节点
class Solution {
int[] nums;
public TreeNode helper(int left, int right) {
if (left > right) return null;
// always choose right middle node as a root
int p = (left + right) / 2;
if ((left + right) % 2 == 1) ++p;
// inorder traversal: left -> node -> right
TreeNode root = new TreeNode(nums[p]);
root.left = helper(left, p - 1);
root.right = helper(p + 1, right);
return root;
}
public TreeNode sortedArrayToBST(int[] nums) {
this.nums = nums;
return helper(0, nums.length - 1);
}
}
选择任意一个中间位置元素作为根节点
不做预定义选择,每次随机选择中间位置左边或者右边元素作为根节点。每次运行的结果都不同,但都是正确的
class Solution {
int[] nums;
Random rand = new Random();
public TreeNode helper(int left, int right) {
if (left > right) return null;
// choose random middle node as a root
int p = (left + right) / 2;
if ((left + right) % 2 == 1) p += rand.nextInt(2);
// inorder traversal: left -> node -> right
TreeNode root = new TreeNode(nums[p]);
root.left = helper(left, p - 1);
root.right = helper(p + 1, right);
return root;
}
public TreeNode sortedArrayToBST(int[] nums) {
this.nums = nums;
return helper(0, nums.length - 1);
}
}
欢迎关注我的公众号,每日更新,督促自己进步