有序序列转平衡搜索树:数组与链表两种输入
摘要
LeetCode 108 和 109 都要求把有序序列转成高度平衡的 二叉搜索树。这两道题表面上几乎一样,一个输入数组,一个输入链表,实际上因为底层存储结构不同,解法细节差异很大。数组支持随机访问,因此可以天然用中点做根;链表不支持随机访问,所以不能直接照抄数组解法。本文会从“为什么取中点会平衡”这个问题出发,比较数组版和链表版的两种主流实现。
第 1 章 LeetCode 108:有序数组转 BST
1.1 为什么中点做根能保证平衡
数组是有序的,如果取中点作为根:
- 左边所有元素都小于根,可构成左子树
- 右边所有元素都大于根,可构成右子树
- 左右两边元素数量尽量接近,因此树高度尽量平衡
这其实是“有序 + 平衡”两个目标的最自然统一。
1.2 代码实现
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;
}这是一道标准分治题:根由中点决定,左右区间继续递归。
第 2 章 LeetCode 109:有序链表转 BST
2.1 为什么不能直接按数组思路做
链表没有下标访问,拿中点必须线性扫描。如果每层递归都从头找中点,虽然能做出来,但效率和代码结构都比数组版差。
2.2 方案一:快慢指针找中点
public TreeNode sortedListToBST(ListNode head) {
return build(head, null);
}
private TreeNode build(ListNode head, ListNode tail) {
if (head == tail) {
return null;
}
ListNode slow = head;
ListNode fast = head;
while (fast != tail && fast.next != tail) {
slow = slow.next;
fast = fast.next.next;
}
TreeNode root = new TreeNode(slow.val);
root.left = build(head, slow);
root.right = build(slow.next, tail);
return root;
}这个写法和数组版思想一致,只是“找中点”从 O(1) 变成了 O(n)。整体复杂度是 。
2.3 方案二:模拟中序构造
更优雅的做法是:
- 先遍历链表求长度
n - 按中序构造 BST
- 左子树建完后,当前链表节点正好对应根
- 再继续建右子树
private ListNode current;
public TreeNode sortedListToBST(ListNode head) {
int length = 0;
current = head;
while (head != null) {
length++;
head = head.next;
}
return buildTree(0, length - 1);
}
private TreeNode buildTree(int left, int right) {
if (left > right) {
return null;
}
int mid = left + (right - left) / 2;
TreeNode leftChild = buildTree(left, mid - 1);
TreeNode root = new TreeNode(current.val);
current = current.next;
root.left = leftChild;
root.right = buildTree(mid + 1, right);
return root;
}这个写法把链表线性顺序和 BST 中序顺序完美对应起来,复杂度可降到 。
第 3 章 两道题真正考的是什么
| 题目 | 输入结构 | 关键操作 | 复杂度 |
|---|---|---|---|
| LC108 | 数组 | O(1) 取中点 | O(n) |
| LC109 | 链表 | 找中点或模拟中序 | O(n log n) 或 O(n) |
这两道题看似是 BST 题,其实也在考你是否尊重底层数据结构的访问成本。
不要忽视“随机访问能力”
同样是有序序列,数组和链表的算法设计差异往往不在逻辑,而在访问成本。把数组思路原样搬到链表上,经常就会把复杂度做坏。
下一篇预告
09 二叉树深度问题:最小深度、最大深度与 BFS-DFS 取舍 会从构造专题回到遍历与返回值专题。最小深度和最大深度经常被放在一起问,但判断逻辑并不对称,这恰好是树题里非常经典的陷阱。