给你一个整数数组 nums,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树
高度平衡 二叉树是一棵满足 [每个节点的左右两个子树的高度差的绝对值不超过 1] 的二叉树
示例1:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
示例2:
输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。
分析
有序数组转二叉搜索树,需要以下几步:
1.找到中间位置
2.用中间位置的元素作为根节点
3.用中间位置左侧的元素构造左子树
4.用中间位置右侧的元素构造右子树
public TreeNode sortedArrayToBST(int[] nums) {
// 找到中间位置索引
// 奇数个元素,则直接是中间位置元素
// 偶数个元素,则可以选择中间的左边一个元素
int midIndex = (0 + (nums.length - 1)) / 2;
// 从中间位置索引拆分数组
int[] leftArr = new int[midIndex];
int[] rightArr = new int[nums.length - 1 - midIndex];
for (int i = 0; i < midIndex; i++) {
leftArr[i] = nums[i];
}
for (int i = midIndex + 1, j = 0; i < nums.length; i++, j++) {
rightArr[j] = nums[i];
}
// 用中间索引位置的元素作为根节点
TreeNode root = new TreeNode(nums[midIndex]);
// 用左侧部分数组构造左子树
root.left = sortedArrayToBST(leftArr);
// 用右侧部分数组构造右子树
root.right = sortedArrayToBST(rightArr);
return root;
}
完整递归算法
public TreeNode sortedArrayToBST(int[] nums) {
// 递归结束条件
if (nums == null || nums.length == 0) {
return null;
}
// 找到中间位置索引
// 奇数个元素,则直接是中间位置元素
// 偶数个元素,则可以选择中间位置左边的元素
int midIndex = (0 + (nums.length - 1)) / 2;
// 从中间位置索引拆分数组
int[] leftArr = new int[midIndex];
int[] rightArr = new int[nums.length - 1 - midIndex];
for (int i = 0; i < midIndex; i++) {
leftArr[i] = nums[i];
}
for (int i = midIndex + 1, j = 0; i < nums.length; i++, j++) {
rightArr[j] = nums[i];
}
// 用中间索引位置的元素作为根节点
TreeNode root = new TreeNode(nums[midIndex]);
// 用左侧部分数组构造左子树
root.left = sortedArrayToBST(leftArr);
// 用右侧部分数组构造右子树
root.right = sortedArrayToBST(rightArr);
return root;
}
递归算法优化分析
第一次计算:
nums: [-10,-3,0,5,9]
中间索引:(0 + 5 - 1)/2 = 2
构造左子树:左子树元素个数 2 ,需取 nums [0,1] 范围的元素
构造右子树:右子树元素个数 2 ,需取 nums [3,4] 范围的元素
第二次计算:
arr1: [-10,-3]
中间索引:(0 + 2 -1)/2 = 0
构造左子树:左子树元素个数 0
构造右子树:右子树元素个数 2,需取 arr1 [0,1] 范围的元素
第三次计算:
arr2: [5,9]
中间索引:(0 + 2 -1)/2 = 0
构造左子树:左子树元素个数 0
构造右子树:右子树元素个数 2,需取 arr2 [0,1] 范围的元素
arr1 [0,1] 范围的元素 实际等同于 nums [0,1] 范围的元素
arr2 [0,1] 范围的元素 实际等同于 nums [3,4] 范围的元素
那么是否可以每次都是从 nums 数组中直接取元素省略构造左侧数组和右侧数组的步骤?
每次可以指定一个左侧索引位置,一个右侧索引位置,每次都是在指定索引范围内从 nums 数组中取元素构建
public TreeNode build(int[] arr, int left, int right) {
// 奇数个元素,则直接是中间位置元素
// 偶数个元素,则可以选择中间位置左边的元素
int midIndex = (left + right) / 2;
TreeNode root = new TreeNode(nums[midIndex]);
// 用左侧部分数组构造左子树
root.left = build(nums, left, midIndex - 1);
// 用右侧部分数组构造右子树
root.right = build(nums, midIndex + 1, right);
return root;
}
优化后的递归算法
public TreeNode sortedArrayToBST(int[] nums) {
return build(nums, 0, nums.length - 1);
}
private TreeNode build(int[] nums, int left, int right) {
if (left > right) {
return null;
}
// 奇数个元素,则直接是中间位置元素
// 偶数个元素,则可以选择中间位置左边的元素
int midIndex = (left + right) / 2;
TreeNode root = new TreeNode(nums[midIndex]);
// 用左侧部分数组构造左子树
root.left = build(nums, left, midIndex - 1);
// 用右侧部分数组构造右子树
root.right = build(nums, midIndex + 1, right);
return root;
}
扩展
偶数个元素是否可以选择中间位置右边的元素?
int midIndex = (left + right + 1) / 2;
偶数个元素是否可以随机取中间位置左边或者右边的元素?
int midIndex = (left + right + rand.nextInt(2)) / 2;