题目
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定的有序链表: [-10, -3, 0, 5, 9],
一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
- 方法一:自顶向下建树
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//快慢指针法查找链表给定区间的中间节点,左闭右开区间
ListNode* getMedian(ListNode* left, ListNode* right){
ListNode *fast = left;
ListNode *slow = left;
while(fast!=right && fast->next!=right){
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
//递归自顶向下建树
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;
}
TreeNode* sortedListToBST(ListNode* head) {
return buildTree(head, NULL);
}
};
-
时间复杂度O(nlogn)
-
空间复杂度O(logn)
-
思路
- 每次选择链表区间最中间的节点作为根节点,再递归建立其左右子树。区间选择左闭右开区间,这样在找到mid之后,其左子树的区间就是[left,mid),其右子树的区间就是[mid->next,right)
- 左闭右开区间,递归函数在left等于right时退出。通过调用查找中值函数,找到根节点的值。再递归调用该函数建造其左右子树,最后返回根节点。
- 查找中值函数传入区间的左边界和右边界,通过快慢指针法找到链表的中间节点,将其返回。
-
方法二:自底向上
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//递归建二叉搜索树
//left和right表示根节点所代表的子树的节点在链表中的范围,取左闭右闭区间
//传入的链表指针为引用类型
TreeNode* buildTree(ListNode*& head, int left, int right){
if(left > right) return NULL;//如果左边界大于右边界返回NULL
//mid划分左右子树,其为根节点
int mid = left + (right-left)/2;
TreeNode *root = new TreeNode();//先建一个根节点占位
root->left = buildTree(head, left, mid-1);//递归建左子树
root->val = head->val;//填根节点的数值
head = head->next;//链表指针后移
root->right = buildTree(head, mid+1, right);//递归建右子树
return root;//返回根节点
}
TreeNode* sortedListToBST(ListNode* head) {
int len = 0;//计算链表的长度
ListNode *p = head;
while(p!=NULL){
++len;
p = p->next;
}
return buildTree(head, 0, len-1);
}
};
- 时间复杂度O(n)
- 空间复杂度O(logn)
- 思路
- 由于中序遍历二叉搜索树得到的结果就是链表本身,故可以将二者结合起来。
- 类似二叉树的中序遍历。
- 先建一个根节点占位,再递归调用该函数建左子树,给当前的根节点赋值,再递归调用建右子树,最后返回根节点。
- 传入参数中的左右边界表示以当前节点为根的子树的数据范围。先计算mid,用它划分左右子树,左子树的范围是[left,mid-1],右子树的范围是[mid, right+1].当左边界大于右边界时,到达了叶子节点的孩子节点,返回空指针。