题目如下:
Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced BST.
分析如下:
第一个想法是可以借用上一题的结论,所以可以很快改出一个代码,提交了。但是上一题的输入是排序好的数组,上一题的时间复杂度是O(N),本题的输入是排序好的链表,因为每次访问元素的时间复杂度为O(lgN), 不可能像访问数组那样是O(1),所以本题的时间复杂度为O(N * lgN)。
一个讨巧的办法是,把list转化为数组,然后再使用上一题的方法,这样使用了额外的空间,可以把时间复杂度变化为O(N)。如下:
//136ms 时间复杂度为O(N * lgN)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* _sortedArrayToBST(vector<int>& num, int start, int end){
if(start==end)
return new TreeNode(num[start]);
if(start>end) {
return NULL;
}
int mid=start+(end-start)/2; //避免溢出
TreeNode* root=new TreeNode(num[mid]);
root->left=_sortedArrayToBST(num, start,mid-1);
root->right=_sortedArrayToBST(num,mid+1,end);
return root;
}
TreeNode *sortedListToBST(ListNode *head) {
ListNode* node=head;
vector<int> vec;
while(node!=NULL){
vec.push_back(node->val);
node=node->next;
}
return _sortedArrayToBST(vec,0,(int)vec.size()-1);
}
};
猜测应该还存在不使用额外空间但是时间复杂度为O(N)的算法,只是我不知道了。。。看了官网,发现一个绝妙的答案,一开始非常难理解,后来想了想,其实是模拟了树的中序遍历的递归版。
/*
模拟树的中序遍历递归版。
*/
116ms
class Solution {
public:
TreeNode *sortedListToBST(ListNode* & head, int start, int end) {
if (start > end) return NULL;
int mid = start + (end - start) / 2;
TreeNode* left = sortedListToBST(head, start, mid - 1);
TreeNode* parent = new TreeNode(head->val);
parent->left = left;
head = head->next;
TreeNode* right = sortedListToBST(head, mid + 1, end);
parent->right = right;
//levelorder_tra(parent);
return parent;
}
TreeNode *sortedListToBST(ListNode *head) {
int length = 0;
ListNode* node = head;
while ( node != NULL) {
length++;
node = node->next;
}
return sortedListToBST(head, 0, length - 1);
}
};
输入链表是升序的,而BST树的中序遍历恰恰也是升序的。这暗示可以模拟BST的中序遍历,来完成从list到tree的转变。所以上面的代码看着眼熟,因为其实实质就是把链表的节点依次拿出来,按照树的中序遍历的顺序构造树,由于链表是升序的,树的中序遍历也是升序的,所以最后形成的就是一颗BST树。
<del>sortedListToBST(ListNode *&head, int start, int end)</del>
在这个函数中,逻辑是这样的。
第一步消耗链表的若干节点创建左子树。
第二步消耗链表的一个节点创建root。
第三步消耗链表的若干节点创建右子树。
为什么第一个参数要写为引用型的呢?正面思考不太容易,反面思考是,如果你不把ListNodde*这个参数写为引用型,那么每次都将消耗head节点,最后建出来的树的所有节点的值都一样了。
这个函数还有一个特点是和上一题建树的方式也不一样,上一题建树的方式是,首先创建root,再创建root的左子树和右子树,是top-down的方式,而本题是先创建了叶子节点,然后再逐渐向上创建根节点,是bottom-up的方式。
具体的方式可以用例子实际去看看就比较清楚了。
假设有 int arr1[ ]={1,2,3} 这3个点等待建树,把上面的函数稍加修改,增加一些辅助的输出,会看得更清楚,比如,每迭代一次,就对树做一次层序遍历。
//辅助函数,进行层序遍历
void levelorder_tra(TreeNode* node){
if(node==NULL)
return;
std::queue<TreeNode*> que1;
que1.push(node);
while(!que1.empty()){
TreeNode* cur_node=que1.front();
if(cur_node==NULL)
std::cout<<"lvl_order_tra->val=#"<<std::endl;
else
std::cout<<"lvl_order_tra->val="<<cur_node->val<<std::endl;
if(cur_node!=NULL){
que1.push(cur_node->left);
que1.push(cur_node->right);
}
que1.pop();
}
return;
}
那么打印出来的结果将是:
i=3watch inside mid=0 ,list->val=1
lvl_order_tra->val=1
lvl_order_tra->val=#
lvl_order_tra->val=#
watch inside mid=1 ,list->val=2
watch inside mid=2 ,list->val=3
lvl_order_tra->val=3
lvl_order_tra->val=#
lvl_order_tra->val=#
lvl_order_tra->val=2
lvl_order_tra->val=1
lvl_order_tra->val=3
lvl_order_tra->val=#
lvl_order_tra->val=#
lvl_order_tra->val=#
lvl_order_tra->val=#
Final Level Order Traversal of the Tree!
lvl_order_tra->val=2
lvl_order_tra->val=1
lvl_order_tra->val=3
lvl_order_tra->val=#
lvl_order_tra->val=#
lvl_order_tra->val=#
lvl_order_tra->val=#
Program ended with exit code: 0
这个解答将是O(N)的时间复杂度,因为它遍历一遍链表就解决了问题了,非常巧妙。
小结扩展:
这道题目改造中序遍历递归版的思路我非常喜欢,很有创造力。另外一道对中序遍历的递归版进行改造的题目是 Recover Binary Search Tree 。