给你一个整数数组 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;
