echo

任生命穿梭 时间的角落

0%

有序列表转换二叉搜索树

109. 有序链表转换二叉搜索树

给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

1
2
3
4
5
6
7
8
9
给定的有序链表: [-10, -3, 0, 5, 9],

一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:

0
/ \
-3 9
/ /
-10 5

我们需要构造出平衡的二叉树,每次让左右子树的结点个数接近,我们可以找出链表元素的中位数作为根节点的值。链表中小于根节点的元素个数与大于根节点的元素个数要么相等,要么相差 1。我们递归地对左右子树进行构造。

设当前链表左端点为 left, 右端点为 right,具体范围为[left, right),左闭右开区间。定义左闭右开区间的好处是,可以方便表示初始列表[head, null),如果使用左闭右闭区间,则第一次需要遍历到链表末尾来获取 right 的值。在找出链表中位数结点 mid后,我们可以使用[left, mid)[mid.next, right)来分别表示左右子树对应的列表。

使用快慢指针法来得到链表的中位数结点,fast 指针每次移动两次,slow 指针每次移动一次,当 fast 到达边界(fast == right 或 fast.next == right,right 的前驱结点为链表的最后一个结点)时停止,此时 slow 指向的元素就是链表的中位数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public TreeNode sortedListToBST(ListNode head) {
//左闭右开区间 [left, right)
return buildTree(head, null);
}

public TreeNode buildTree(ListNode left, ListNode right){
if(left == right){
return null;
}
//得到中间结点
ListNode mid = getMid(left, right);
TreeNode root = new TreeNode(mid.val);
//递归构造左右子树
root.left = buildTree(left, mid);
root.right = buildTree(mid.next, right);

return root;
}
//快慢指针法
public ListNode getMid(ListNode left, ListNode right){
ListNode slow = left;
ListNode fast = left;

while(fast != right && fast.next != right){
slow = slow.next;
fast = fast.next;
fast = fast.next;
}

return slow;
}
}

时间复杂度O(n logn),空间复杂度O(log n)。 n 是链表的长度。

寻找链表的中位数是一个比较耗时的操作,在二叉搜索树的构建过程是一个中序遍历,遍历结果就是链表,我们可以使用一个指针 ptr 来指向下一个需要建立的链表结点,在构建二叉搜索树的同时不断将 ptr 向后移动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
ListNode ptr;

public TreeNode sortedListToBST(ListNode head) {
ptr = head;
int length = getLength(head);
//左闭右闭区间 [left, right]
return buildTree(0, length - 1);
}
//得到链表的长度
public int getLength(ListNode head){
int ret = 0;
while(head != null){
head = head.next;
++ret;
}
return ret;
}

public TreeNode buildTree(int left, int right){
if(left > right){
return null;
}
//计算 mid 位置
int mid = (left + right) / 2;
TreeNode root = new TreeNode();
//先构建左子树
root.left = buildTree(left, mid - 1);
//左子树构建完时,ptr 指向根节点
root.val = ptr.val;
ptr = ptr.next;
//构建右子树
root.right = buildTree(mid + 1, right);
return root;
}

}

时间复杂度O(n),空间复杂度O(log n)。n 为链表的长度。