大家好🙌我是你们的好朋友,程序员乌拉😀。相遇是缘,既然来了就拎着小板凳坐下来一起唠会儿😎,如果在文中有所收获,请别忘了一键三连,动动你发财的小手👍,你的鼓励,是我创作的动力😁!废话不多说,直接😎 开干吧!
==PS:文末干货,记得拎着小板凳离开的时候也给它顺走== 🤣 座右铭:“懒”对一个人的毁灭性有多大,早起的重要性就多大。
有序链表转换为二叉搜索树
题目
给出一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
高度平衡二叉搜索树: ==指一个二叉树每个节点的左右两个子树的高度差的绝对值不要超过1==
示例1:
有序链表转换为二叉搜索树
// 给定的有序链表:[-10, -3, 0, 5, 9]
答案可能是:[0, -3, 9, -10, null, 5],如上图可以表示高度平衡二叉搜索树
方法1:分治
解析:
将给定的有序链表转换为二叉搜索树的第一步是确定根节点。
1、由于需要构造出平衡的二叉树,因此比较直观的想法是让根节点左子树中的节点个数与右子树中的节点个数尽可能接近。
2、这样一来,左右子树的高度也会非常接近,可以达到高度差绝对值不超过 1 的要求。
如何找出这样的一个根节点呢?
1、可以找出链表元素的中位数作为根节点的值。
2、这里对于中位数的定义为:如果链表中的元素个数为奇数,那么唯一的中间值为中位数;如果元素个数为偶数,那么唯二的中间值都可以作为中位数,而不是常规定义中二者的平均值。
3、根据中位数的性质,链表中小于中位数的元素个数与大于中位数的元素个数要么相等,要么相差 1。此时,小于中位数的元素组成了左子树,大于中位数的元素组成了右子树,它们分别对应着有序链表中连续的一段。使用分治的思想,继续递归地对左右子树进行构造,找出对应的中位数作为根节点,以此类推。
解题流程
1、具体地,设当前链表的左端点为 left,右端点right,包含关系为「左闭右开」,即 left 包含在链表中而 right 不包含在链表中。达到快速地找出链表的中位数节点 mid 的效果。
为什么要设定「左闭右开」的关系?
1、由于题目中给定的链表为单向链表,访问后继元素十分容易,但无法直接访问前驱元素。因此在找出链表的中位数节点 mid。
2、如果设定「左闭右开」的关系,可以直接用 (left,mid) 以及 (mid.next,right) 来表示左右子树对应的列表了。并且,初始的列表也可以用 (head,null) 方便地进行表示,其中 null 表示空节点。
快慢指针法。
1、初始时,快指针 fast 和慢指针 slow 均指向链表的左端点 left。
2、将快指针 fast 向右移动两次的同时,将慢指针 slow 向右移动一次,直到快指针到达边界(即快指针到达右端点或快指针的下一个节点是右端点)。
3、此时,慢指针对应的元素就是中位数。在找出了中位数节点之后,将其作为当前根节点的元素,并递归地构造其左侧部分的链表对应的左子树,以及右侧部分的链表对应的右子树。
Java实现代码
class Solution {
public TreeNode sortedListToBST(ListNode head) {
return buildTree(head, null);
}
public TreeNode buildTree(ListNode left, ListNode right) {
if (left == right) {
return null;
}
ListNode mid = getMedian(left, right);
TreeNode root = new TreeNode(mid.val);
root.left = buildTree(left, mid);
root.right = buildTree(mid.next, right);
return root;
}
public ListNode getMedian(ListNode left, ListNode right) {
ListNode fast = left;
ListNode slow = left;
while (fast != right && fast.next != right) {
fast = fast.next;
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
复杂度分析
-
时间复杂度:O(nlogn),其中 n 是链表的长度。设长度为 n 的链表构造二叉搜索树的时间为 T(n),递推式为 T(n)=2⋅T(n/2)+O(n),根据主定理,T(n)=O(nlogn)。
-
空间复杂度:O(logn),这里只计算除了返回答案之外的空间。平衡二叉树的高度为 O(logn),即为递归过程中栈的最大深度,也就是需要的空间。
Python实现代码
class Solution:
def sortedListToBST(self, head: ListNode) -> TreeNode:
def getMedian(left: ListNode, right: ListNode) -> ListNode:
fast = slow = left
while fast != right and fast.next != right:
fast = fast.next.next
slow = slow.next
return slow
def buildTree(left: ListNode, right: ListNode) -> TreeNode:
if left == right:
return None
mid = getMedian(left, right)
root = TreeNode(mid.val)
root.left = buildTree(left, mid)
root.right = buildTree(mid.next, right)
return root
return buildTree(head, None)
class ListNode(object):
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class TreeNode(object):
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
方法2:分治 + 中序遍历优化
时间复杂度的瓶颈在于寻找中位数节点。由于构造出的二叉搜索树的中序遍历结果就是链表本身,因此可以将分治和中序遍历结合起来,减少时间复杂度。
具体地,设当前链表的左端点编号为 left,右端点编号为 right,包含关系为「双闭」,即 left 和 right 均包含在链表中。链表节点的编号为 [0,n)。中序遍历的顺序是「左子树 - 根节点 - 右子树」。
在分治的过程中,不用急着找出链表的中位数节点,而是使用一个占位节点,等到中序遍历到该节点时,再填充它的值。
通过计算编号范围来进行中序遍历:
1、中位数节点对应的编号为 mid=(left+right+1)/2;根据方法一中对于中位数的定义,编号为 (left+right)/2 的节点同样也可以是中位数节点。
2、左右子树对应的编号范围分别为 [left,mid−1] 和 [mid+1,right]。如果 left>right,那么遍历到的位置对应着一个空节点,否则对应着二叉搜索树中的一个节点。
Java实现代码
class Solution {
ListNode globalHead;
public TreeNode sortedListToBST(ListNode head) {
globalHead = head;
int length = getLength(head);
return buildTree(0, length - 1);
}
public int getLength(ListNode head) {
int ret = 0;
while (head != null) {
++ret;
head = head.next;
}
return ret;
}
public TreeNode buildTree(int left, int right) {
if (left > right) {
return null;
}
int mid = (left + right + 1) / 2;
TreeNode root = new TreeNode();
root.left = buildTree(left, mid - 1);
root.val = globalHead.val;
globalHead = globalHead.next;
root.right = buildTree(mid + 1, right);
return root;
}
}
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
复杂度分析
-
时间复杂度:O(nlogn),其中 n 是链表的长度。设长度为 n 的链表构造二叉搜索树的时间为 T(n),递推式为 T(n)=2⋅T(n/2)+O(n),根据主定理,T(n)=O(nlogn)。
-
空间复杂度:O(logn),这里只计算除了返回答案之外的空间。平衡二叉树的高度为 O(logn),即为递归过程中栈的最大深度,也就是需要的空间。
Python实现代码
class Solution:
def sortedListToBST(self, head: ListNode) -> TreeNode:
def getLength(head: ListNode) -> int:
ret = 0
while head:
ret += 1
head = head.next
return ret
def buildTree(left: int, right: int) -> TreeNode:
if left > right:
return None
mid = (left + right + 1) // 2
root = TreeNode()
root.left = buildTree(left, mid - 1)
nonlocal head
root.val = head.val
head = head.next
root.right = buildTree(mid + 1, right)
return root
length = getLength(head)
return buildTree(0, length - 1)
class ListNode(object):
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class TreeNode(object):
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
文末彩蛋🤩
🚗🤩😉💖🌹👀✨给各位朋友安利一下平时收集的各种学习资料!!!有需要的朋友点击一下⏩传送门,自行领取。程序员经典名言:"收藏了就等于学会啦"。 做人也要像蜡烛一样,在有限的一生中有一分热发一份光,给人以光明,给人以温暖! 图灵程序丛书300+ Linux实战100讲 Linux书籍 计算机基础硬核总结 计算机基础相关书籍 操作系统硬核总结 Java自学宝典 Java学习资料 Java硬核资料 Java面试必备 Java面试深度剖析 阿里巴巴Java开发手册 MySQL入门资料 MySQL进阶资料 深入浅出的SQL Go语言书籍