题目
给定一个单链表的头节点 head
,其中的元素 按升序排序 ,将其转换为平衡二叉搜索树。
分析
分治
我们将链表中间偏左的结点$m*$作为根节点构建BST,由此将原问题规约为两个规模更小的子问题,分别递归求解,得到左子树和右子树,将其和根结点组合后返回即可。
代码
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
| class Solution { public: ListNode *findMid(ListNode *head, ListNode *end) { ListNode *slow = head, *fast = head; while (fast->next != end && fast->next->next != end) { slow = slow->next; fast = fast->next->next; } return slow; } TreeNode *buildBST(ListNode *head, ListNode *end) { if (head == end) return nullptr; auto mid = findMid(head, end); TreeNode *root = new TreeNode(mid->val); root->left = buildBST(head, mid); root->right = buildBST(mid->next, end); return root; } TreeNode* sortedListToBST(ListNode* head) { return buildBST(head, nullptr); } };
|
时间复杂度
每次递归的时候,为了找到中间的结点,我们需要$O(n)$的时间。
由此得到递推方程$W(n) = 2W(n/2) + O(n).$
由主定理,解得$W(n) = \Theta(nlogn).$
空间复杂度
空间复杂度为递归深度$O(logn).$
优化
可以看到,由于链表不具有随机访问的能力,我们为了找到中间结点,需要花费$O(n)$的时间。
为了减少这个时间,一种可能的方法是以空间换时间,将链表中的元素存储进数组中,这样就可以在$O(1)$的时间内访问到中间结点了,但是这样会导致空间复杂度变为$O(n)$.
那有什么办法可以既保证空间复杂度不改变,又能够降低时间复杂度呢?
观察建树的过程,我们先初始化根结点,然后去递归的建立左子树和右子树,但建立左子树的过程中,根结点的唯一作用就是作为边界,实际只会用到根节点左边的结点。因此,我们可以用下标来代替根节点,先建立左子树,然后建立根结点,之后再建立右子树。这个过程很类似中序遍历的过程,因此我们可以再每次递归的时候,将头结点后移,这样在递归处理完左子树之后,头结点的位置刚好指向我们需要的中间结点的位置,从而无需花费$O(n)$的时间复杂度去定位中间节点。
代码:
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
| class Solution { public: int getLength(ListNode *head) { int length = 0; while (head) { ++length; head = head->next; } return length; } TreeNode *buildBST(ListNode *&head, int l, int r) { if (l > r) return nullptr; auto root = new TreeNode(); int m = l + ((r - l) >> 1); root->left = buildBST(head, l, m - 1); root->val = head->val; head = head->next; root->right = buildBST(head, m + 1, r); return root; } TreeNode* sortedListToBST(ListNode* head) { int length = getLength(head); return buildBST(head, 0, length - 1); } };
|
时间复杂度
递推方程$W(n) = 2W(n/2) + O(1).$
由主定理,$W(n) = \Theta(n).$
空间复杂度
空间复杂度等于递归深度$O(logn).$