当前位置: 首页 > news >正文

108. 将有序数组转换为二叉搜索树

文章目录

  • 108. 将有序数组转换为二叉搜索树
  • 思路
    • 递归
  • 总结

108. 将有序数组转换为二叉搜索树

108. 将有序数组转换为二叉搜索树

给你一个整数数组 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 <= nums.length <= 10^4
    -10^4 <= nums[i] <= 10^4
  • nums 按 严格递增 顺序排列

思路

做这道题目之前大家可以了解一下这几道(知道如何划分左右子区间去,并用上层的LeftRight接住上来的节点):

106. 从中序与后序遍历序列构造二叉树

654.最大二叉树中其实已经讲过了,如果根据数组构造一棵二叉树。

701.二叉搜索树中的插入操作

450.删除二叉搜索树中的节点

进入正题:

题目中说要转换为一棵高度平衡二叉搜索树。为什么强调要平衡呢?

因为只要给我们一个有序数组,如果不强调平衡,都可以以线性结构来构造二叉搜索树。

例如 有序数组[-10,-3,0] 就可以构造成这样的二叉搜索树,如图。

在这里插入图片描述

上图中,是符合二叉搜索树的特性吧,如果要这么做的话,是不是本题意义就不大了,所以才强调是平衡二叉搜索树。

其实数组构造二叉树,构成平衡树是自然而然的事情,因为大家默认都是从数组中间位置取值作为节点元素,一般不会随机取。所以想构成不平衡的二叉树是自找麻烦。

在654.最大二叉树中其实已经讲过了,如果根据数组构造一棵二叉树。

本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间。

本题其实要比654.最大二叉树简单一些,因为有序数组构造二叉搜索树,寻找分割点就比较容易了。

分割点就是数组中间位置的节点。

那么为问题来了,如果数组长度为偶数,中间节点有两个,取哪一个?

取哪一个都可以,只不过构成了不同的平衡二叉搜索树。

例如:输入:[-10,-3,0,5,9]

如下两棵树,都是这个数组的平衡二叉搜索树:
在这里插入图片描述

如果要分割的数组长度为偶数的时候,中间元素为两个,是取左边元素 就是树1,取右边元素就是树2

这也是题目中强调答案不是唯一的原因。 理解这一点,这道题目算是理解到位了。

递归

递归三部曲:

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

相信大家如果仔细看下面两篇文章后

701.二叉搜索树中的插入操作
450.删除二叉搜索树中的节点

一定会对递归函数返回值的作用深有感触。

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

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

所以代码如下:

// 左闭右闭区间[left, right]
func dfs(nums []int,left,right int)*TreeNode{}

这里注意,我这里定义的是左闭右闭区间,在不断分割的过程中,也会坚持左闭右闭的区间,这又涉及到我们讲过的循环不变量(即开始定好了左闭右闭,则在递归的过程中要全程坚持这个区间划分原则,否则区间会划乱,导致计算结果不对)。

2.确定递归终止条件
这里定义的是左闭右闭的区间,所以当区间 left > right时,说明区间没有元素了,就可以返回空节点了。

代码如下:

// 当前区间已经没有元素,可以递归结束返回nil,让上层递归的Left或者Right接住它if left > right {return nil}

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

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

取了中间位置,就开始以中间位置的元素构造节点,代码: root := &TreeNode{Val:nums[mid]}

接着划分区间,root的左孩子接住下一层左区间的构造节点,右孩子接住下一层右区间构造的节点。最后返回root节点

单层递归整体代码如下:

	mid := left + (right - left) / 2root := &TreeNode{Val:nums[mid]}root.Left  = dfs(nums,left,mid - 1)root.Right = dfs(nums,mid+1,right)return root

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

递归整体Go代码如下:

/*** Definition for a binary tree node.* type TreeNode struct {*     Val int*     Left *TreeNode*     Right *TreeNode* }*/
func sortedArrayToBST(nums []int) *TreeNode {// 由于是要构造平衡的二叉搜索树,所以两边的高度最多相差1,因此我们应该选数组的中间位置元素作为根节点// 这样左右子树的元素才接近,且满足根节点大于左子树所有节点,小于右子树所有节点if len(nums) == 0 {return nil}// 左闭右闭root := dfs(nums,0,len(nums) - 1)return root}func dfs(nums []int,left,right int)*TreeNode{// 当前区间已经没有元素,可以递归结束返回nil,让上层递归的Left或者Right接住它if left > right {return nil}mid := left + (right - left) / 2root := &TreeNode{Val:nums[mid]}root.Left  = dfs(nums,left,mid - 1)root.Right = dfs(nums,mid+1,right)return root
}

注意:在调用dfs的时候传入的leftright为什么是0len(nums) - 1,因为定义的区间为左闭右闭
在这里插入图片描述

总结

在二叉树专题中,已经写了多道构造二叉树的题目了,其实思路也是一样的,不断中间分割,然后递归处理左区间,右区间,也可以说是分治。

此时相信大家应该对通过递归函数的返回值来增删二叉树很熟悉了,这也是常规操作。

在定义区间的过程中我们又一次强调了循环不变量的重要性。


http://www.mrgr.cn/news/40500.html

相关文章:

  • 【Router】T750路由功能之VLAN划分功能介绍及实现
  • 【Simulink仿真】两级式三相光伏并网发电系统
  • PDSCH(物理下行共享信道)简介
  • 企业架构系列(15)ArchiMate第13节:战略视角
  • Python Selenium常用语法汇总(包含XPath语法)
  • 如何从硬盘恢复丢失/删除的视频
  • Linux学习笔记(二):深入理解用户管理、运行级别与命令行操作
  • 【深度学习基础模型】玻尔兹曼机BM|受限玻尔兹曼机RBM|深度置信网络DBN详细理解并附实现代码。
  • 昇思MindSpore进阶教程--使能图算融合
  • redis 中IO多路复用与Epoll函数
  • SpringBoot项目 | 瑞吉外卖 | 短信发送验证码功能改为免费的邮箱发送验证码功能 | 代码实现
  • YOLOv8改进 ,YOLOv8改进主干网络为华为的轻量化架构GhostNetV1
  • Vortex GPGPU的github流程跑通与功能模块波形探索
  • 【艾思科蓝】Vue.js组件开发实战:从零构建高效可复用组件
  • 基于SpringBoot+Vue的学生宿舍管理系统
  • 2024/10/1 操作系统大题专训之文件
  • Sqoop实战-- Sqoop的Job任务、增量导入、数据格式转换与Lombok的使用指南
  • 基于C++和Python的进程线程CPU使用率监控工具
  • 谈谈英国论文写作复合句式的运用
  • 一种支持4种多模态RAG技术的引擎:VARAG