LeetCode 109. 有序链表转换二叉搜索树
题目描述


题意分析
给定一条按升序排列的单链表,使用其中全部节点值构造一棵高度平衡的二叉搜索树,返回树的根节点。高度平衡要求每个节点的左右子树高度差都不超过一,不只是整棵树的根满足这个条件。
合法结果可能不唯一,关键是同时满足有序性和平衡性。链表只能沿后继顺序访问,不能像数组一样直接读取中间位置;因此需要解决如何均分树的结构,又不反复扫描链表寻找中点。
解法:按中序顺序构造平衡 BST
核心思路
[!blue]
有序链表从头到尾的值,正好可以作为目标二叉搜索树的中序序列。可以先根据节点数量安排一棵平衡树的形状,再按照“左子树、根、右子树”的顺序依次填入链表值,就不需要在链表中随机取中点。
先遍历一次得到长度
n。递归函数build(left, right)中的闭区间表示这一棵子树要占用哪些中序位置,区间本身不访问链表。选择中点mid作为根的位置,左侧分配给左子树,右侧分配给右子树。两侧节点数最多相差一,并继续以相同方式均分,因此构造出的各层都能保持高度平衡。用共享游标
current指向下一个尚未取值的链表节点。进入某个递归区间时,它指向这个区间的第一个值。先递归构造[left, mid - 1],左子树正好取走前面的全部值;返回后,游标自然来到中点对应的值,此时才能创建根节点,并将游标推进一次。接着构造
[mid + 1, right],消耗右侧剩余值,挂到根的右边。函数返回时,恰好取完当前区间的所有节点,游标停在下一个区间的起点。父调用可以继续使用它,不需要从头查找或把链表复制到数组。当
left > right时,区间为空,直接返回空树且不消耗链表节点。对于空链表,初始区间就是[0, -1],会自然得到空树。构造过程中只读取链表值和后继,原链表连接保持不变。
解题步骤
- 将
current指向链表头,遍历统计节点数n。- 调用
build(0, n - 1);递归中若left > right,返回空节点。- 计算中点
mid,先构造左侧区间,保存得到的左子树。- 用此时
current的值创建根节点,并令current = current.next。- 连接已建好的左子树,再递归构造并连接右侧区间,返回当前根。
代码实现
class Solution {
private ListNode current;
public TreeNode sortedListToBST(ListNode head) {
current = head;
int length = 0;
for (ListNode node = head; node != null; node = node.next) {
length++;
}
return build(0, length - 1);
}
private TreeNode build(int left, int right) {
if (left > right) {
return null;
}
int mid = left + (right - left) / 2;
TreeNode leftChild = build(left, mid - 1);
// 中序位置对应当前链表节点。
TreeNode root = new TreeNode(current.val);
current = current.next;
root.left = leftChild;
root.right = build(mid + 1, right);
return root;
}
}
func sortedListToBST(head *ListNode) *TreeNode {
length := 0
for node := head; node != nil; node = node.Next {
length++
}
current := head
var build func(int, int) *TreeNode
build = func(left int, right int) *TreeNode {
if left > right {
return nil
}
mid := left + (right-left)/2
leftChild := build(left, mid-1)
// 构造顺序与 BST 中序遍历顺序一致。
root := &TreeNode{Val: current.Val}
current = current.Next
root.Left = leftChild
root.Right = build(mid+1, right)
return root
}
return build(0, length-1)
}
复杂度分析
- 时间复杂度:$O(n)$,统计长度遍历一次,建树时每个链表节点再取值一次,每个树节点只做常量操作。
- 空间复杂度:$O(\log n)$,不计返回的树。递归每次均分区间,栈深为对数量级;新建的结果树另占 $O(n)$。
关键点总结
[!green]
- 区间中点只决定根的位置和左右规模,实际节点值始终从链表游标顺序取得。
- 先建左子树再取根值,才能让链表的升序顺序与树的中序顺序一一对应。
- 每个递归调用恰好消费自己的区间长度,返回时为父调用保留正确的下一个取值位置。
易错点总结
[!yellow]
- 在构造左子树之前取根值,会把当前区间最小的值放到根上,破坏中序顺序。
- 用取值游标统计长度后不重置,会在建树开始时已经走到空节点;当前代码使用另一个指针计数。
- 把游标作为普通指针参数传递,却不把递归后的新位置返回或共享,父调用无法看到子调用的推进。
- 空区间条件写成
left >= right会丢掉单节点子树,正确条件是严格大于。- 初始闭区间右端应为
n - 1;多分配一个位置,会在链表耗尽后继续取值。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 108. 将有序数组转换为二叉搜索树 | 简单 | 有序数组可直接定位中点,本题链表可先计长度,再按中序顺序消耗节点建树。 |
| 1382. 将二叉搜索树变平衡 | 中等 | 同样根据有序中序序列构造平衡BST,输入来源与取数方式不同。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!