LeetCode(109)Convert Sorted List to Binary Search Tree

本文深入解析如何利用中序遍历递归改造解决链表转换为高度平衡二叉搜索树的问题,通过将链表转化为数组并应用前一题的解决方案,或直接在链表上模拟树的中序遍历过程,实现时间复杂度为O(N)的高效算法。详细阐述了两种方法的实现逻辑,并对比了它们在时间和空间复杂度上的优劣。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目如下:

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=3
watch 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 。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值