【面试高频题】难度 1/5,经典树的搜索(多语言)

题目描述

这是 LeetCode 上的 109. 有序链表转换二叉搜索树 ,难度为 中等

Tag : 「二叉树」、「树的搜索」、「分治」、「中序遍历」

给定一个单链表的头节点 head,其中的元素 按升序排序 ,将其转换为高度平衡的二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差不超过 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1。

示例 1:

ini 复制代码
输入: head = [-10,-3,0,5,9]

输出: [0,-3,9,-10,null,5]

解释: 一个可能的答案是[0,-3,9,-10,null,5],它表示所示的高度平衡的二叉搜索树。

示例 2:

ini 复制代码
输入: head = []

输出: []

提示:

  • head 中的节点数在 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 0 , 2 × 1 0 4 ] [0, 2 \times 10^4] </math>[0,2×104] 范围内
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> − 1 0 5 < = N o d e . v a l < = 1 0 5 -10^5 <= Node.val <= 10^5 </math>−105<=Node.val<=105

递归分治

与上一题 108. 将有序数组转换为二叉搜索树 类似,但链表相对于数组,无法 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1) 找到构建当前 BST 根节点的"中点"下标。

一个仍可确保 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n) 时间复杂度(瓶颈在于需要创建 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 个节点),但需要 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n) 空间复杂度的做法是:对链表进行一次遍历,转成数组后套用上一题的做法。

一个不使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n) 空间复杂度的做法,需要每次遍历来找"中点"下标:起始我们先对 head 进行一次遍历,得到链表长度 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n,随后仍然利用递归分治的思路进行构造,每次对入参的左右端点找"中点",先通过直接结算的方式定位到偏移量 <math xmlns="http://www.w3.org/1998/Math/MathML"> t = l + ⌊ l + r 2 ⌋ t = l + \left \lfloor \frac{l + r}{2} \right \rfloor </math>t=l+⌊2l+r⌋,然后再通过从入参节点 head 出发往前走 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t 的做法找到"中点"节点。

该做法每个节点的访问次数为在递归过程中被 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ l , r ] [l, r] </math>[l,r] 所覆盖的次数,我们知道一个节点数量为 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 的平衡 BST 树高为 <math xmlns="http://www.w3.org/1998/Math/MathML"> log ⁡ n \log{n} </math>logn,因此整体复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n log ⁡ n ) O(n\log{n}) </math>O(nlogn)。

Java 代码:

Java 复制代码
class Solution {
    public TreeNode sortedListToBST(ListNode head) {
        int n = 0;
        ListNode cur = head;
        while (cur != null && ++n >= 0) cur = cur.next;
        return build(head, 0, n - 1);
    }
    TreeNode build(ListNode head, int l, int r) {
        if (l > r) return null;
        int mid = l + r >> 1, t = mid - l;
        ListNode cur = head;
        while (t-- > 0) cur = cur.next;
        TreeNode ans = new TreeNode(cur.val);
        ans.left = build(head, l, mid - 1);
        ans.right = build(cur.next, mid + 1, r);
        return ans;
    }
}

Python 代码:

Python 复制代码
class Solution:
    def sortedListToBST(self, head: Optional[ListNode]) -> Optional[TreeNode]:
        n = 0
        cur = head
        while cur:
            n += 1
            cur = cur.next
        return self.build(head, 0, n - 1)
        
    def build(self, head: ListNode, l: int, r: int) -> TreeNode:
        if l > r:
            return None
        mid = l + r >> 1
        t = mid - l
        cur = head
        while t > 0:
            cur = cur.next
            t -= 1
        ans = TreeNode(cur.val)
        ans.left = self.build(head, l, mid - 1)
        ans.right = self.build(cur.next, mid + 1, r)
        return ans

C++ 代码:

C++ 复制代码
class Solution {
public:
    TreeNode* sortedListToBST(ListNode* head) {
        int n = 0;
        ListNode* cur = head;
        while (cur && ++n >= 0) cur = cur->next;
        return build(head, 0, n - 1);
    }
    TreeNode* build(ListNode* head, int l, int r) {
        if (l > r) return nullptr;
        int mid = l + r >> 1, t = mid - l;
        ListNode* cur = head;
        while (t-- > 0) cur = cur->next;
        TreeNode* ans = new TreeNode(cur->val);
        ans->left = build(head, l, mid - 1);
        ans->right = build(cur->next, mid + 1, r);
        return ans;
    }
};

TypeScript 代码:

TypeScript 复制代码
function sortedListToBST(head: ListNode | null): TreeNode | null {
    const build = function (head: ListNode | null, l: number, r: number): TreeNode | null {
        if (l > r) return null;
        let mid = l + r >> 1, t = mid - l;
        let cur = head;
        while (t-- > 0) cur = cur.next;
        const ans = new TreeNode(cur!.val);
        ans.left = build(head, l, mid - 1);
        ans.right = build(cur!.next, mid + 1, r);
        return ans;
    }
    let n = 0;
    let cur = head;
    while (cur != null && ++n >= 0) cur = cur.next;       
    return build(head, 0, n - 1);
}
  • 时间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n log ⁡ n ) O(n\log{n}) </math>O(nlogn)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)

递归分治 - 中序遍历

由于给定的 nums 本身严格有序,而 BST 的中序遍历亦是有序。因此我们可以一边遍历链表,一边对 BST 进行构造。

具体的,我们仍然先对链表进行遍历,拿到链表长度 n。递归构造过程中传入左右端点 lr,含义为使用链表中 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ l , r ] [l, r] </math>[l,r] 部分节点,但不再在每次递归中传入当前头结点,而是使用全局变量 head 来记录。

递归构造过程中,计算"中点"位置 <math xmlns="http://www.w3.org/1998/Math/MathML"> m i d = ⌊ l + r 2 ⌋ mid = \left \lfloor \frac{l + r}{2} \right \rfloor </math>mid=⌊2l+r⌋,并根据如下流程进行构造:

  1. 使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ l , m i d − 1 ] [l, mid - 1] </math>[l,mid−1] 构建左子树,使用变量 left 保存当前左子树的根节点
  2. 构建完左子树后,全局变量 head 必然来到了"中点"位置,用其构建根节点 ans,并将根节点与此前构造的 left 关联。同时让链表节点 head 后移
  3. 使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ m i d + 1 , r ] [mid + 1, r] </math>[mid+1,r] 构建右子树,并将其挂载到根节点 ans

如此一来,即可确保「链表遍历」和「BST 构造」的同步性。

Java 代码:

Java 复制代码
class Solution {
    ListNode head;
    public TreeNode sortedListToBST(ListNode _head) {
        head = _head;
        int n = 0;
        ListNode cur = head;
        while (cur != null && ++n >= 0) cur = cur.next;
        return build(0, n - 1);
    }
    TreeNode build(int l, int r) {
        if (l > r) return null;
        int mid = l + r >> 1;
        TreeNode left = build(l, mid - 1); 
        TreeNode ans = new TreeNode(head.val);
        head = head.next;
        ans.left = left;
        ans.right = build(mid + 1, r);
        return ans;
    }
}

Python 代码:

Python 复制代码
class Solution:
    def sortedListToBST(self, head: Optional[ListNode]) -> Optional[TreeNode]:
        self.head = head
        n = 0
        cur = self.head
        while cur is not None:
            n += 1
            cur = cur.next
        return self.build(0, n - 1)

    def build(self, l: int, r: int) -> TreeNode:
        if l > r:
            return None
        mid = l + r >> 1
        left = self.build(l, mid - 1)
        ans = TreeNode(self.head.val)
        ans.left = left
        self.head = self.head.next
        ans.right = self.build(mid + 1, r)
        return ans

C++ 代码:

C++ 复制代码
class Solution {
public:
    ListNode* head;
    TreeNode* sortedListToBST(ListNode* _head) {
        head = _head;
        int n = 0;
        ListNode* cur = head;
        while (cur && n++ >= 0) cur = cur->next;
        return build(0, n - 1);
    }
    TreeNode* build(int l, int r) {
        if (l > r) return nullptr;
        int mid = l + r >> 1;
        TreeNode* left = build(l, mid - 1);
        TreeNode* ans = new TreeNode(head->val);
        ans->left = left;
        head = head->next;
        ans->right = build(mid + 1, r);
        return ans;
    }
};

TypeScript 代码:

TypeScript 复制代码
function sortedListToBST(_head: ListNode | null): TreeNode | null {
    const build =function(l: number, r: number): TreeNode | null {
        if (l > r) return null;
        const mid = l + r >> 1;
        const left = build(l, mid - 1);
        const ans = new TreeNode(head.val);
        ans.left = left;
        head = head.next;
        ans.right = build(mid + 1, r);
        return ans;
    }
    let head = _head;
    let n = 0, cur = head;
    while (cur && n++ >= 0) cur = cur.next;
    return build(0, n - 1);
}
  • 时间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( log ⁡ n ) O(\log{n}) </math>O(logn)

最后

这是我们「刷穿 LeetCode」系列文章的第 No.109 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour...

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

相关推荐
lu_rong_qq1 小时前
决策树 DecisionTreeClassifier() 模型参数介绍
算法·决策树·机器学习
qq_364371721 小时前
Vue 内置组件 keep-alive 中 LRU 缓存淘汰策略和实现
前端·vue.js·缓存
y先森2 小时前
CSS3中的弹性布局之侧轴的对齐方式
前端·css·css3
Estar.Lee6 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
LNTON羚通7 小时前
摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现
算法·目标检测·音视频·监控·视频监控
y先森7 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy7 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189117 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿8 小时前
CSS查缺补漏(补充上一条)
前端·css
2401_857610038 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全