Leetcode 109 Convert Sorted List to Binary Search Tree 有序单链表构造平衡二插查找树

原题地址:https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/

题目描述

Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced BST.
给出一个有序的单链表(递增),构造出一个平衡二叉查找树。

解题思路

简单的动态规划,跟 Leetcode 108 Convert Sorted Array to Binary Search Tree 类似,不同的地方是,这里给出的单链表,不知道链表长度而且不能快速随机访问到元素节点。为此我们需要简单修改代码逻辑,我们遍历一次单链表,获取其节点个数n,然后使用head和n作为输入来构造二叉树,取n/2为中间节点,从head开始找到这个点p,构造根节点,然后使用p之前的n/2个(大概值,后面再精确确定)节点构造左子树,使用p之后的n/2个(大概值,后面再精确确定)节点构造右子树。

由于每次找到中间点的时候都需要从head开始遍历一半元素,且大概共需递归调用O(log(n))次,因此时间复杂度为O(nlog(n))。

算法描述

  1. 遍历单链表,获取链表长度n;
  2. 计算中间节点n/2,从head开始找到中间节点,构造根节点;
  3. 使用中间节点以左的n/2个节点,递归构造左子树;
  4. 使用中间节点以右的n/2个节点,递归构造右子树。

对于选取中间节点之后,左侧节点个数和右侧节点个数的精确值问题,我们不妨举几个实例来求得其通用的表达式,我们以[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]为例,按照递归调用的层级分析:

1. 第一层:链表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],n=10, n/2=5, 所以选择第5个元素5,这时候其左侧有5个数,右侧有4个数;5=10-5,即nLeft=n-n/2, 4=10-5-1,即nRight=n-n/2-1;

2. 第二层(左侧为例):链表[0, 1, 2, 3, 4],n=5,n/2=2,所以选择第2个元素2,这时候其左侧有2个数,右侧有2个数;2=5-2-1,即nLeft=nRight=n-n/2-1;

3. 第三层(左侧为例):链表[0, 1],n=2,n/2=1,所以选择第1个元素1,这时其左侧有1个数,右侧有0个数;1=2-1,即nLeft=n-n/2,0=2-1-1,即nRight=n-n/2-1;

4. 第四层(左侧):链表[0],n=1,n/2=0,所以选择第0个元素0,这时其左侧有0个数,右侧有0个数;0=1-1,即nLeft=nRight=n-n/2-1。

总结如上规律,当有n个数时,先选择第n/2个数作为根节点的值,然后其左侧元素的个数与n是否为奇数有关,nLeft = n & 1 ? n - n / 2 - 1 : n - n / 2 或者 nLeft = n - n / 2 - (n & 1);其右侧元素的个数恒等于 n - n / 2 - 1

代码 c

/**
 * 根据有序(递增)链表构造平衡二叉查找树
 * input head : 链表首节点
 * return     : 平衡二叉查找树
 */
struct TreeNode* sortedListToBST(struct ListNode* head) {
    struct TreeNode* sortedListSeqToBST(struct ListNode*, int);
    if (head == NULL) return NULL;

    /* 统计链表节点个数 */
    int count = 0;
    struct ListNode* p = head;
    while (p) {
        ++count;
        p = p->next;
    }

    return sortedListSeqToBST(head, count);
}

/**
 * 根据有序(递增)链表片段构造平衡二叉树
 * input head  : 链表起始节点
 * input count : 链表片段节点的个数
 * return      : 平衡二叉查找树
 */
struct TreeNode* sortedListSeqToBST(struct ListNode* head, int count) {
    /* 如果链表片段中没有节点,直接返回NULL */
    if (count <= 0) return NULL;

    /* 找到当前要作为TreeNode根节点的值的ListNode */
    int center = count / 2, i = 0;
    struct ListNode* p = head;
    while (i++ < center) p = p->next; // p最终指向的节点的值作为根节点的值

    /* 创建根节点 */
    struct TreeNode* node = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    node->val = p->val; // 记录根节点的值

    /* 利用p之前的节点,构造左子树 */
    node->left = sortedListSeqToBST(head, count - center - (count & 1));

     /* 利用p之后的节点,构造右子树 */
    node->right = sortedListSeqToBST(p->next, count - center - 1);

    return node;
}

完整代码:包含判定结果是否为平衡二插查找数的方法
https://github.com/Orange1991/leetcode/blob/master/109/c/main.c

运行情况

Status:Accept
Time:8ms

代码 cpp

与c版本基本相同。

class Solution {
public:
    /**
     * 根据有序(递增)链表构造平衡二叉查找树
     * input head : 链表首节点
     * return     : 平衡二叉查找树
     */
    struct TreeNode* sortedListToBST(struct ListNode* head) {
        if (head == NULL) return NULL;

        /* 统计链表节点个数 */
        int count = 0;
        struct ListNode* p = head;
        while (p) {
            ++count;
            p = p->next;
        }

        return sortedListSeqToBST(head, count);
    }

private:
    /**
     * 根据有序(递增)链表片段构造平衡二叉树
     * input head  : 链表起始节点
     * input count : 链表片段节点的个数
     * return      : 平衡二叉查找树
     */
    struct TreeNode* sortedListSeqToBST(struct ListNode* head, int count) {
        /* 如果链表片段中没有节点,直接返回NULL */
        if (count <= 0) return NULL;

        /* 找到当前要作为TreeNode根节点的值的ListNode */
        int center = count / 2, i = 0;
        struct ListNode* p = head;
        while (i++ < center) p = p->next; // p最终指向的节点的值作为根节点的值

        /* 创建根节点 */
        TreeNode* node = new TreeNode(p->val); // 记录根节点的值

        /* 利用p之前的节点,构造左子树 */
        node->left = sortedListSeqToBST(head, n & 1 ? count - center - 1 : count - center);

        /* 利用p之后的节点,构造右子树 */
        node->right = sortedListSeqToBST(p->next, count - center - 1);

        return node;
     }
};

完整代码,包含判定是否为平衡二插查找树的方法
https://github.com/Orange1991/leetcode/blob/master/109/cpp/main.cpp)

运行情况

Status:Accept
Time:28ms


// sfg1991@163.com
// 2015/6/5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值