【LeetCode刷题日记】108.将有序数组转换为二叉搜索树

🔥个人主页:北极的代码(欢迎来访)

🎬作者简介:java后端学习者

❄️个人专栏:苍穹外卖日记SSM框架深入JavaWeb

命运的结局尽可永在,不屈的挑战却不可须臾或缺!

前言:

大家好,我是代码不加冰,又到了我们每日的刷题环节,今天刷的题目是关于二叉搜索树的,让我们一起来看看吧。


题目背景:108.将有序数组转换成二叉搜索树

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。

示例 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 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums严格递增 顺序排列

题目解析:

解题思路

核心思想 :因为数组是有序的,BST 的中序遍历就是升序序列。要构建高度平衡的 BST,每次选择数组中间的元素作为根节点,这样左右子树的元素个数相差不超过 1。

递归步骤

  1. 如果数组区间无效(left > right),返回 null

  2. 找到中间位置 mid = left + (right - left) / 2

  3. 创建根节点 new TreeNode(nums[mid])

  4. 递归构建左子树:root.left = build(left, mid - 1)

  5. 递归构建右子树:root.right = build(mid + 1, right)

  6. 返回根节点

递归三部曲:

确定递归函数返回值及其参数

删除二叉树节点,增加二叉树节点,都是用递归函数的返回值来完成,这样是比较方便的。

相信大家如果仔细看了前面的文章,一定会对递归函数返回值的作用深有感触。

那么本题要构造二叉树,依然用递归函数的返回值来构造中节点的左右孩子。

再来看参数,首先是传入数组,然后就是左下标left和右下标right,我们在前面中提过,在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下标来操作原数组。

所以代码如下:

复制代码
// 左闭右闭区间[left, right]
TreeNode* traversal(vector<int>& nums, int left, int right)

这里注意,我这里定义的是左闭右闭区间,在不断分割的过程中,也会坚持左闭右闭的区间,这又涉及到我们讲过的循环不变量

确定递归终止条件

这里定义的是左闭右闭的区间,所以当区间 left > right的时候,就是空节点了。

代码如下:

复制代码
if (left > right) return nullptr;
  • 确定单层递归的逻辑

首先取数组中间元素的位置,不难写出int mid = (left + right) / 2;这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在二分法中尤其需要注意

所以可以这么写:int mid = left + ((right - left) / 2);

直觉写法(有bug)

大多数人第一反应是:

java 复制代码
java

int mid = (left + right) / 2;

问题 :当 leftright 都很大时(比如接近 Integer.MAX_VALUE),left + right 可能会溢出 ,变成负数,导致 mid 计算错误。


方法2:安全写法(防溢出)

java 复制代码
java

int mid = left + (right - left) / 2;

推导过程

java 复制代码
text

目标:求 left 和 right 的中点

(left + right) / 2 
= left/2 + right/2
= left + (right - left)/2   ← 变形

验证 :假设 left = 4, right = 10

java 复制代码
text

方式1:(4 + 10) / 2 = 14 / 2 = 7
方式2:4 + (10 - 4) / 2 = 4 + 6/2 = 4 + 3 = 7

防溢出原理

  • right - left 最大值 ≈ 数组长度 ≤ 10^4,不会溢出

  • left + 一个小数 也不会溢出

最后返回root节点,单层递归整体代码如下:

复制代码
int mid = left + ((right - left) / 2);
TreeNode* root = new TreeNode(nums[mid]);
root->left = traversal(nums, left, mid - 1);
root->right = traversal(nums, mid + 1, right);
return root;

这里int mid = left + ((right - left) / 2);的写法相当于是如果数组长度为偶数,中间位置有两个元素,取靠左边的。

图解示例

输入nums = [-10, -3, 0, 5, 9]

构建过程
java 复制代码
text

第1步:left=0, right=4, mid=2, root=0
                   0
                 /   \
          左子树      右子树
      [-10,-3]      [5,9]

第2步:左子树 left=0, right=1, mid=0, root=-10
                   0
                 /   \
               -10    右子树
                 \
                 -3

第2步:右子树 left=3, right=4, mid=3, root=5
                   0
                 /   \
               -10    5
                 \     \
                 -3     9

最终结果(一种可能的平衡 BST):

text

复制代码
      0
     / \
   -10  5
     \   \
     -3   9

为什么这样构建是平衡的
性质 说明
中间元素作根 左右子树元素个数差 ≤ 1
递归构建 每个子树也都是平衡的
BST 性质 左子树所有值 < 根 < 右子树所有值(因为数组升序)

复杂度分析
  • 时间复杂度:O(n),每个节点只访问一次

  • 空间复杂度:O(log n),递归栈深度(平衡二叉树的高度)


多种中间点选择方式

代码中 int mid = left + (right - left) / 2 选择的是左中位数。你也可以选择右中位数:

java 复制代码
java

// 右中位数(数组长度为偶数时选靠右的)
int mid = left + (right - left + 1) / 2;

不同的选择会产生不同形状的 BST,但都是平衡的。例如 [-10,-3,0,5,9]

中间点选择 根节点
左中位数 (index 2) 0
右中位数 (index 3) 5

题目答案:

递归法:

java 复制代码
class Solution {
    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 mid = left + (right - left) / 2;
        TreeNode root = new TreeNode(nums[mid]);
        
        // 递归构建左右子树
        root.left = build(nums, left, mid - 1);
        root.right = build(nums, mid + 1, right);
        
        return root;
    }
}

迭代法:

java 复制代码
class Solution {
	public TreeNode sortedArrayToBST(int[] nums) {
		if (nums.length == 0) return null;

		//根节点初始化
		TreeNode root = new TreeNode(-1);
		Queue<TreeNode> nodeQueue = new LinkedList<>();
		Queue<Integer> leftQueue = new LinkedList<>();
		Queue<Integer> rightQueue = new LinkedList<>();

		// 根节点入队列
		nodeQueue.offer(root);
		// 0为左区间下标初始位置
		leftQueue.offer(0);
		// nums.size() - 1为右区间下标初始位置
		rightQueue.offer(nums.length - 1);

		while (!nodeQueue.isEmpty()) {
			TreeNode currNode = nodeQueue.poll();
			int left = leftQueue.poll();
			int right = rightQueue.poll();
			int mid = left + ((right - left) >> 1);

			// 将mid对应的元素给中间节点
			currNode.val = nums[mid];

			// 处理左区间
			if (left <= mid - 1) {
				currNode.left = new TreeNode(-1);
				nodeQueue.offer(currNode.left);
				leftQueue.offer(left);
				rightQueue.offer(mid - 1);
			}

			// 处理右区间
			if (right >= mid + 1) {
				currNode.right = new TreeNode(-1);
				nodeQueue.offer(currNode.right);
				leftQueue.offer(mid + 1);
				rightQueue.offer(right);
			}
		}
		return root;
	}
}
相关推荐
Arenaschi1 小时前
关于GPT的版特点
java·网络·人工智能·windows·python·gpt
橙淮1 小时前
并发编程(五)
java
Dlrb12111 小时前
数据结构-排序算法
数据结构·算法·排序算法·插入排序·堆排序·希尔排序·快速排序
过期动态1 小时前
【LeetCode 热题 100】无重复字符的最长子串
java·数据结构·spring boot·算法·leetcode·职场和发展
Yeats_Liao2 小时前
好复杂的 IoT 世界:工业数据采集技术栈全景解析
java·物联网·struts
月落归舟2 小时前
Java线程小记
java·开发语言
西凉的悲伤2 小时前
Spring Cloud Gateway介绍
java·spring cloud·gateway
逸Y 仙X2 小时前
文章五:Elasticsearch安全通信
java·大数据·安全·elasticsearch·搜索引擎·全文检索·jenkins
quan26312 小时前
20260529,日常开发-查老数据全量更新闭坑
java·mysql·主从·主从延迟