一. 思路应该和leetcode108_将有序数组转换为二叉搜索树差不多, 但是需要找中点, 没数组高效, 看题解如何处理.
作者:LeetCode
链接:https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree/solution/you-xu-lian-biao-zhuan-huan-er-cha-sou-suo-shu-by-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二. 方法 1:递归
1. 当前方法和下一个方法的主要思路是:给定列表中的中间元素将会作为二叉搜索树的根,该点左侧的所有元素递归的去构造左子树,同理右侧的元素构造右子树。这必然能够保证最后构造出的二叉搜索树是平衡的。
2. 由于我们得到的是一个有序链表而不是数组,我们不能直接使用下标来访问元素。我们需要知道链表中的中间元素。
3. 我们可以利用两个指针来访问链表中的中间元素。假设我们有两个指针 slow和 fast. slow每次向后移动一个节点而 fast每次移动两个节点。当 fast到链表的末尾时 slow 就访问到链表的中间元素。对于一个偶数长度的数组,中间两个元素都可用来作二叉搜索树的根。
4. 当找到链表中的中间元素后,我们将链表从中间元素的左侧断开,做法是使用一个 prev_ptr 的指针记录 slow_ptr 之前的元素,也就是满足 prev_ptr.next = slow_ptr。断开左侧部分就是让 prev_ptr.next = None。
5. 我们只需要将链表的头指针传递给转换函数,进行高度平衡二叉搜索树的转换。所以递归调用的时候,左半部分我们传递原始的头指针;右半部分传递 slow_ptr.next 作为头指针。
#include <iostream>
using namespace std;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
ListNode* findMiddleElement(ListNode* head) {
ListNode* pre = NULL;
ListNode* slow = head;
ListNode* fast = head;
//链表的中点用快慢指针.
while (fast != NULL&&fast->next != NULL) {
pre = slow;
slow = slow->next;
fast = fast->next->next;
}
//如果slow不是head, 则将pre的next指向NULL.
//因为要分开成左链表以及右链表.
if (pre != NULL) pre->next = NULL;
return slow;
}
TreeNode* sortedListToBST(ListNode* head) {
if (head == NULL) return NULL;
//首先找到中点.
ListNode* mid = findMiddleElement(head);
TreeNode* root = new TreeNode(mid->val);
//特殊考虑链表只有一个元素特殊情况,不然死循环.
if (head == mid) return root;
root->left = sortedListToBST(head);
root->right = sortedListToBST(mid->next);
return root;
}
};
可以仔细看看这个复杂度分析.......
方法 2:递归 + 转成数组
1. 这个方法是空间换时间的经典案例。2. 在这个方法中,我们将给定的链表转成数组并利用数组来构建二叉搜索树。数组找中间元素只需要 O(1) 的时间,所以会降低整个算法的时间复杂度开销。
2. 将给定链表转成数组,将数组的头和尾记成 left 和 right 。找到中间元素 (left + right) / 2,记为 mid。这需要O(1) 时间开销,也是与上面算法主要改进的地方。将中间元素作为二叉搜索树的根。递归构造二叉搜索树的左右两棵子树,两个子数组分别是 (left, mid - 1) 和 (mid + 1, right)。
3. 作者Java代码也很好理解.
/**
* Definition for singly-linked list. public class ListNode { int val; ListNode next; ListNode(int
* x) { val = x; } }
*/
/**
* Definition for a binary tree node. public class TreeNode { int val; TreeNode left; TreeNode
* right; TreeNode(int x) { val = x; } }
*/
class Solution {
private List<Integer> values;
public Solution() {
this.values = new ArrayList<Integer>();
}
private void mapListToValues(ListNode head) {
while (head != null) {
this.values.add(head.val);
head = head.next;
}
}
private TreeNode convertListToBST(int left, int right) {
// Invalid case
if (left > right) {
return null;
}
// Middle element forms the root.
int mid = (left + right) / 2;
TreeNode node = new TreeNode(this.values.get(mid));
// Base case for when there is only one element left in the array
if (left == right) {
return node;
}
// Recursively form BST on the two halves
node.left = convertListToBST(left, mid - 1);
node.right = convertListToBST(mid + 1, right);
return node;
}
public TreeNode sortedListToBST(ListNode head) {
// Form an array out of the given linked list and then
// use the array to form the BST.
this.mapListToValues(head);
// Convert the array to
return convertListToBST(0, this.values.size() - 1);
}
}
作者:LeetCode
链接:https ://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree/solution/you-xu-lian-biao-zhuan-huan-er-cha-sou-suo-shu-by-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处
复杂度分析
1. 时间复杂度:时间复杂度降到了 O(N) ,因为需要将链表转成数组。而取中间元素的开销变成了 O(1) 所以整体的时间复杂度降低了。
2. 空间复杂度:因为我们利用额外空间换取了时间复杂度的降低,空间复杂度变成了O(N),相较于之前算法的 O(logN) 有所提升,因为创建数组的开销。
方法 3:中序遍历模拟
1. 我们知道,二叉树有三种不同的遍历方法:前序遍历 中序遍历 和 后序遍历。
2. 中序遍历一棵二叉搜索树会有一个非常有趣的结论。中序遍历一棵二叉搜索树的结果是得到一个升序序列。
3. 基于解决这个问题的中序遍历的思想:我们知道中序遍历最左边的元素一定是给定链表的头部,类似地下一个元素一定是链表的下一个元素,以此类推。这是肯定的因为给定的初始链表保证了升序排列。
3. 遍历整个链表获得它的长度,我们用两个指针标记结果数组的开始和结束,记为 start 和 end,他们的初始值分别为 0 和 length - 1。
4. 记住,我们当前需要模拟中序遍历,找到中间元素 (start + end) / 2。注意这里并不需要在链表中找到确定的元素是哪个,只需要用一个变量告诉我们中间元素的下标。我们只需要递归调用这两侧。
5. 递归左半边,其中开始和结束的值分别为 start, mid - 1。
6. 在这个算法中,每当我们构建完二叉搜索树的左半部分时,链表中的头指针将指向根节点或中间节点(它成为根节点)。 因此,我们只需使用头指针指向的当前值作为根节点,并将指针后移一位,即 head = head.next。
7. 我们在递归右半部分 mid + 1, end。
//中序遍历模拟
class Solution {
public:
int findSize(ListNode* head) {
ListNode* cur = head;
int size = 0;
while (cur != NULL) {
cur = cur->next;
size++;
}
return size;
}
//一定要定义全局变量的指针,不然指针不会变化.
ListNode* constHead;
TreeNode* convertListToBST(int left, int right) {
if (left > right) return NULL;
int mid = left + (right - left) / 2;
//模仿中序遍历,左右递归二叉树.
//中序遍历的值是链表的开头.
//可以这么做的原因是mid左边一定是左子树,
//mid右边一定是右子树.
TreeNode* leftTree = convertListToBST(left, mid - 1);
TreeNode* root = new TreeNode(constHead->val);
root->left = leftTree;
constHead = constHead->next;
root->right = convertListToBST(mid + 1, right);
return root;
}
TreeNode* sortedListToBST(ListNode* head) {
if (head == NULL) return NULL;
//先算出链表的长度.
int size = findSize(head);
//全局指针指向head.
constHead = head;
return convertListToBST(0, size - 1);
}
};
复杂度分析
时间复杂度:时间复杂度仍然为 O(N) 因为我们需要遍历链表中所有的顶点一次并构造相应的二叉搜索树节点。
空间复杂度:O(logN) ,额外空间只有一个递归栈,由于是一棵高度平衡的二叉搜索树,所以高度上界为 logN。