二叉搜索树题目:将有序链表转换为二叉搜索树

文章目录

  • 题目
    • 标题和出处
    • 难度
    • 题目描述
      • 要求
      • 示例
      • 数据范围
  • 前言
  • 解法一
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法二
    • 思路和算法
    • 代码
    • 复杂度分析

题目

标题和出处

标题:将有序链表转换为二叉搜索树

出处:109. 将有序链表转换为二叉搜索树

难度

5 级

题目描述

要求

给定单链表的头结点 head \texttt{head} head,其中元素已经按升序排列,将其转换为高度平衡二叉搜索树。

高度平衡二叉树满足每个结点的左右子树的高度差的绝对值不超过 1 \texttt{1} 1

示例

示例 1:

示例 1

输入: head = [-10,-3,0,5,9] \texttt{head = [-10,-3,0,5,9]} head = [-10,-3,0,5,9]
输出: [0,-3,9,-10,null,5] \texttt{[0,-3,9,-10,null,5]} [0,-3,9,-10,null,5]
解释:一个可能的答案是 [0,-3,9,-10,null,5] \texttt{[0,-3,9,-10,null,5]} [0,-3,9,-10,null,5],表示如图所示的高度平衡二叉搜索树。

示例 2:

输入: head = [] \texttt{head = []} head = []
输出: [] \texttt{[]} []

数据范围

  • 链表 head \texttt{head} head 中结点数目在范围 [0, 2 × 10 4 ] \texttt{[0, 2} \times \texttt{10}^\texttt{4}\texttt{]} [0, 2×104]
  • -10 5 ≤ Node.val ≤ 10 5 \texttt{-10}^\texttt{5} \le \texttt{Node.val} \le \texttt{10}^\texttt{5} -105Node.val105

前言

这道题和「将有序数组转换为二叉搜索树」相似,区别在于这道题给定有序链表。

为了得到高度平衡二叉搜索树,构造的二叉搜索树应满足根结点的左子树和右子树的结点数尽可能接近,左子树和右子树也都是高度平衡二叉搜索树。

解法一

思路和算法

由于二叉搜索树的中序遍历序列是单调递增的,因此给定的升序链表即为二叉搜索树的中序遍历序列。在只有中序遍历序列的情况下,无法唯一地确定二叉搜索树。

为了得到高度平衡二叉搜索树,构造的二叉搜索树应满足根结点的左子树和右子树的结点数尽可能接近。当结点总数是奇数时,根结点值应为中序遍历序列的中间位置的结点值,根结点的左子树和右子树的结点数应相等;当结点总数是偶数时,根结点值应为中序遍历序列的中间位置的两个结点值之一,根结点的左子树和右子树的结点数之差的绝对值应等于 1 1 1

确定高度平衡二叉搜索树的根结点之后,其余的结点值分别位于根结点的左子树和右子树中,链表中位于根结点左侧的值都在左子树中,链表中位于根结点右侧的值都在右子树中,左子树和右子树也是高度平衡二叉搜索树。可以通过数学归纳法证明,如果两个高度平衡二叉搜索树的结点数之差的绝对值不超过 1 1 1,则这两个高度平衡二叉搜索树的高度之差的绝对值不超过 1 1 1

由于高度平衡二叉搜索树的每个子树也都是高度平衡二叉搜索树,每个子树包含的结点值的集合对应给定的链表中的连续子链表,因此可以使用递归分治的方式构造高度平衡二叉搜索树,递归的过程中只要指定每个子树包含的结点值的集合对应的连续子链表的区间 [ start , end ) [\textit{start}, \textit{end}) [start,end) 即可,该区间是左闭右开区间,区间包含 start \textit{start} start,不包含 end \textit{end} end

递归的终止条件是区间为空,即 start = end \textit{start} = \textit{end} start=end,此时对应的子树为空。对于其余情况,首先根据 start \textit{start} start end \textit{end} end 定位到子链表区间的中间结点并使用中间结点值创建根结点,然后分别使用中间结点左边和右边的两个区间创建根结点的左子树和右子树。定位到子链表区间的中间结点可以通过快慢指针实现。

start ≠ end \textit{start} \ne \textit{end} start=end 时,中间结点的唯一性取决于区间 [ start , end ) [\textit{start}, \textit{end}) [start,end) 内的元素个数的奇偶性。如果区间 [ start , end ) [\textit{start}, \textit{end}) [start,end) 内的元素个数是奇数,则中间结点是唯一的;如果区间 [ start , end ) [\textit{start}, \textit{end}) [start,end) 内的元素个数是偶数,则中间结点是不唯一的,可以是中间位置左边的结点或者中间位置右边的结点。

由此可以得到三种构造高度平衡二叉搜索树的方法。

  • 每次都将根结点值取为中间位置左边的结点值。

  • 每次都将根结点值取为中间位置右边的结点值。

  • 每次随机将根结点值取为中间位置左边或右边的结点值。

代码

下面的代码为每次都将根结点值取为中间位置左边的结点值的做法。

class Solution {public TreeNode sortedListToBST(ListNode head) {return createBST(head, null);}public TreeNode createBST(ListNode start, ListNode end) {if (start == end) {return null;}ListNode slow = start, fast = start.next;while (fast != end && fast.next != end) {slow = slow.next;fast = fast.next.next;}return new TreeNode(slow.val, createBST(start, slow), createBST(slow.next, end));}
}

下面的代码为每次都将根结点值取为中间位置右边的结点值的做法。

class Solution {public TreeNode sortedListToBST(ListNode head) {return createBST(head, null);}public TreeNode createBST(ListNode start, ListNode end) {if (start == end) {return null;}ListNode slow = start, fast = start;while (fast != end && fast.next != end) {slow = slow.next;fast = fast.next.next;}return new TreeNode(slow.val, createBST(start, slow), createBST(slow.next, end));}
}

下面的代码为每次随机将根结点值取为中间位置左边或右边的结点值的做法。

class Solution {Random random = new Random();public TreeNode sortedListToBST(ListNode head) {return createBST(head, null);}public TreeNode createBST(ListNode start, ListNode end) {if (start == end) {return null;}ListNode slow = start, fast = start.next;while (fast != end && fast.next != end) {slow = slow.next;fast = fast.next.next;}if (fast != end && random.nextBoolean()) {slow = slow.next;}return new TreeNode(slow.val, createBST(start, slow), createBST(slow.next, end));}
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是链表 head \textit{head} head 的长度。用 T ( n ) T(n) T(n) 表示用长度为 n n n 的链表构造高度平衡二叉搜索树的时间,则有 T ( n ) = 2 × T ( n 2 ) + O ( n ) T(n) = 2 \times T\Big(\dfrac{n}{2}\Big) + O(n) T(n)=2×T(2n)+O(n),根据主定理可知 T ( n ) = O ( n log ⁡ n ) T(n) = O(n \log n) T(n)=O(nlogn)

  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 n n n 是链表 head \textit{head} head 的长度。空间复杂度主要是递归调用的栈空间,由于构造的是高度平衡二叉搜索树,因此递归调用栈的深度是 O ( log ⁡ n ) O(\log n) O(logn)。注意返回值不计入空间复杂度。

解法二

思路和算法

解法一需要多次定位中间结点,因此时间复杂度较高。由于给定的升序链表即为二叉搜索树的中序遍历序列,因此可以在遍历链表的过程中构造高度平衡二叉搜索树。

首先遍历链表得到链表的长度 n n n,则高度平衡二叉搜索树对应的下标区间是 [ 0 , n − 1 ] [0, n - 1] [0,n1]。对于下标区间 [ start , end ] [\textit{start}, \textit{end}] [start,end],将该区间对应的子树的根结点值的下标记为 mid \textit{mid} mid,则有 mid = ⌊ start + end 2 ⌋ \textit{mid} = \Big\lfloor \dfrac{\textit{start} + \textit{end}}{2} \Big\rfloor mid=2start+end 或者 mid = ⌊ start + end + 1 2 ⌋ \textit{mid} = \Big\lfloor \dfrac{\textit{start} + \textit{end} + 1}{2} \Big\rfloor mid=2start+end+1,左子树和右子树分别对应下标区间 [ start , mid − 1 ] [\textit{start}, \textit{mid} - 1] [start,mid1] [ mid + 1 , end ] [\textit{mid} + 1, \textit{end}] [mid+1,end]

由于中序遍历序列的方法为依次遍历左子树、根结点和右子树,因此在遍历链表的过程中依次构造左子树、根结点和右子树,遍历链表结束时,高度平衡二叉搜索树构造完毕。

由于当下标区间 [ start , end ] [\textit{start}, \textit{end}] [start,end] 内的元素个数是偶数时, mid \textit{mid} mid 的取值不唯一,因此同样存在三种构造高度平衡二叉搜索树的方法。

  • 每次都将根结点值取为中间位置左边的结点值。

  • 每次都将根结点值取为中间位置右边的结点值。

  • 每次随机将根结点值取为中间位置左边或右边的结点值。

代码

下面的代码为每次都将根结点值取为中间位置左边的结点值的做法。

class Solution {ListNode curr;public TreeNode sortedListToBST(ListNode head) {curr = head;int length = 0;ListNode node = head;while (node != null) {length++;node = node.next;}return createBST(0, length - 1);}public TreeNode createBST(int start, int end) {if (start > end) {return null;}int mid = (end - start) / 2 + start;TreeNode left = createBST(start, mid - 1);int rootVal = curr.val;curr = curr.next;TreeNode right = createBST(mid + 1, end);return new TreeNode(rootVal, left, right);}
}

下面的代码为每次都将根结点值取为中间位置右边的结点值的做法。

class Solution {ListNode curr;public TreeNode sortedListToBST(ListNode head) {curr = head;int length = 0;ListNode node = head;while (node != null) {length++;node = node.next;}return createBST(0, length - 1);}public TreeNode createBST(int start, int end) {if (start > end) {return null;}int mid = (end - start + 1) / 2 + start;TreeNode left = createBST(start, mid - 1);int rootVal = curr.val;curr = curr.next;TreeNode right = createBST(mid + 1, end);return new TreeNode(rootVal, left, right);}
}

下面的代码为每次随机将根结点值取为中间位置左边或右边的结点值的做法。

class Solution {Random random = new Random();ListNode curr;public TreeNode sortedListToBST(ListNode head) {curr = head;int length = 0;ListNode node = head;while (node != null) {length++;node = node.next;}return createBST(0, length - 1);}public TreeNode createBST(int start, int end) {if (start > end) {return null;}int mid = (end - start + random.nextInt(2)) / 2 + start;TreeNode left = createBST(start, mid - 1);int rootVal = curr.val;curr = curr.next;TreeNode right = createBST(mid + 1, end);return new TreeNode(rootVal, left, right);}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是链表 head \textit{head} head 的长度。用 T ( n ) T(n) T(n) 表示用长度为 n n n 的链表构造高度平衡二叉搜索树的时间,则有 T ( n ) = 2 × T ( n 2 ) + O ( 1 ) T(n) = 2 \times T\Big(\dfrac{n}{2}\Big) + O(1) T(n)=2×T(2n)+O(1),根据主定理可知 T ( n ) = O ( n ) T(n) = O(n) T(n)=O(n)

  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 n n n 是链表 head \textit{head} head 的长度。空间复杂度主要是递归调用的栈空间,由于构造的是高度平衡二叉搜索树,因此递归调用栈的深度是 O ( log ⁡ n ) O(\log n) O(logn)。注意返回值不计入空间复杂度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/548900.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

士兵排列问题

解法一&#xff1a; deque实现队头入队和队尾入队即可得到编号排列&#xff0c;每个士兵有二个属性&#xff1a;编号、能力值。 #include<iostream> #include<algorithm> #include<deque> #include<vector> using namespace std; #define endl \n st…

苍穹外卖-day04:项目实战-套餐管理(新增套餐,分页查询套餐,删除套餐,修改套餐,起售停售套餐)业务类似于菜品模块

苍穹外卖-day04 课程内容 新增套餐套餐分页查询删除套餐修改套餐起售停售套餐 要求&#xff1a; 根据产品原型进行需求分析&#xff0c;分析出业务规则设计接口梳理表之间的关系&#xff08;分类表、菜品表、套餐表、口味表、套餐菜品关系表&#xff09;根据接口设计进行代…

搜索二叉树迭代和递归的两种*简单*实现方式

判断搜索二叉树 概念 一棵树所有结点的左节点小于父节点&#xff0c;右节点大于父节点&#xff0c;则为搜索二叉树。 迭代方法 中序遍历二叉树&#xff0c;如果总是升序则是搜索二叉树。如果存在降序&#xff0c;那肯定不是搜索二叉树。 Coding checkTreeOrder()方法 boo…

python 基础知识点(蓝桥杯python科目个人复习计划65)

今日复习内容&#xff1a;做题 例题1&#xff1a;遥远的雪国列车 问题描述&#xff1a; 小蓝和小红今天在房间里一起看完了“雪国列车”这部电影&#xff0c;看完之后他们感触颇深&#xff0c;同时他们想到了这样一道题目&#xff1a; 现在有一个数轴&#xff0c;长度为N&a…

代码随想录算法训练营第二十五天|216.组合总和III,17.电话号码的字母组合

216.组合总和III 题目 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数&#xff0c;并且每种组合中不存在重复的数字。 说明&#xff1a; 所有数字都是正整数。 解集不能包含重复的组合。 示例 1: 输入: k 3, n 7 输出: [[1,2,4]] 示例 2: 输入…

java方法的引用传递和值传递

1、方法的值参数传递 下面代码&#xff0c;它会在控制台输出什么&#xff1f; public class ArrayTest {public static void main(String[] args) {int number 100;System.out.println(number);change(number);System.out.println(number);}public static void change(int n…

想要了解更多商品信息?淘宝天猫详情接口API助你一键搞定!

想要了解更多商品信息&#xff1f;淘宝天猫详情接口API是你的理想选择&#xff01;作为唯一提供官方商品数据的接口&#xff0c;它能够帮助你快速获取商品的多种详细信息&#xff0c;联讯数据让你在购物过程中做出更明智的决策。 简介&#xff1a;淘宝天猫详情接口API的功能及…

【C语言_数组_复习篇】

目录 一、数组的概念 二、数组的类型 三、一维数组 3.1 一维数组的创建 3.2 一维数组的初始化 3.3 一维数组的访问 3.4 一维数组在内存中的存储 四、二维数组 4.1 二维数组的创建 4.2 二维数组的初始化 4.3 二维数组的访问 4.4 二维数组在内存中的存储 五、字符串数组 5.1…

干货分享,大厂内部压测方案设计!

01、为什么要做压测 1、什么是压力测试&#xff1f; 不断向被测对象施加压力&#xff0c;测试系统在压力情况下的表现。 2、压力测试的目的是什么&#xff1f; 测试得出系统的极限性能指标&#xff0c;从而给出合理的承诺值或者容量告警&#xff1b; 找出系统的性能瓶颈&a…

检查约束

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 检查约束 检查约束指的是在数据列上设置一些过滤条件&#xff0c;当过滤条件满足的时候才可以进行保存&#xff0c;如果不满足则出现错误。例如&#xff0c;设置年龄的信息…

低成本3D打印上位机,wifi增强器(棒子),刷Klipper

WIFI增强器(棒子)刷写Klipper&#xff0c;作为3D打印机的上位机。 本文中采用的棒子&#xff1a;骁龙410 512mb4g 1.下载安装必要软件 下载地址&#xff1a;https://pan.baidu.com/s/1QjYFknUYGnVMKYablFnZfQ?pwdukr9 提取码&#xff1a;ukr9 下载完成后总共四个文件 …

Go微服务实战——服务的注册与获取(nacos做服务注册中心)

背景 随着访问量的逐渐增大&#xff0c;单体应用结构渐渐不满足需求&#xff0c;在微服务出现之后引用被拆分为一个个的服务&#xff0c;服务之间可以互相访问。初期服务之间的调用只要知道服务地址和端口即可&#xff0c;而服务会出现增减、故障、升级等变化导致端口和ip也变…