044二叉搜索树中第K小的元素

二叉搜索树中第K小的元素

题目链接:https://leetcode.cn/problems/kth-smallest-element-in-a-bst/description/?envType=study-plan-v2\&envId=top-100-liked

我的解答:

复制代码
public int kthSmallest(TreeNode root, int k) {
    Deque<TreeNode> stack = new LinkedList<>();
    int cnt = 0;
    while(!stack.isEmpty() || root != null){
        while(root != null){
            stack.push(root);
            root = root.left;
        }
        root = stack.poll();
        cnt++;
        if(cnt == k){
            return root.val;
        }
        root = root.right;
    }
    return 0;
}

分析:

​ 1、代码的时间复杂度为O(height+k),空间复杂度为O(height),height为树的高度。解题思路:利用"搜索二叉树的中序遍历是升序序列"的性质,利用栈对搜索二叉树进行升序遍历,并记录中序遍历到的节点个数,当遍历到第k个节点时,这个节点的值就是答案。

​ 2、进阶:如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?

​ 答:初步想法是维护一个双向链表,该链表记录了搜索二叉树中序遍历的顺序,每次插入或删除元素,我们直接根据插入或删除的节点改变链表节点之间的联系即可。(看了官方题解后,知道需要自己实现平衡二叉搜索树(AVL树)来实现)

看了官方题解后的解答:

复制代码
//方法一:中序遍历
//时间复杂度:O(H+k),H为树的高度,在开始遍历之前,我们需要O(H)到达叶结点
//空间复杂度:O(H),H为树的高度
public int kthSmallest(TreeNode root, int k) {
    Deque<TreeNode> stack = new LinkedList<>();
    while(!stack.isEmpty() || root != null){
        while(root != null){
            stack.push(root);
            root = root.left;
        }
        root = stack.poll();
        k--;
        if(k == 0){
            break;
        }
        root = root.right;
    }
    return root.val;
}

//方法二(只看思路自己实现版,不太正确):记录子树的节点数(解决问题:如果你需要频繁地查找第 k 小的值,你将如何优化算法?)
//时间复杂度:O(H+k),H为树的高度,在开始遍历之前,我们需要O(H)到达叶结点
//空间复杂度:O(H),H为树的高度
public int kthSmallest(TreeNode root, int k) {
    //key:当前节点  value:当前节点的左子树的节点个数
    Map<TreeNode,Integer> map = new HashMap<>();
    Deque<TreeNode> stack = new LinkedList<>();
    int cnt = 0;//当前节点遍历之前,中序遍历到的节点个数,即当前节点左子树的节点个数
    TreeNode cur = root;
    while(!stack.isEmpty() || cur !=null){
        while(cur!=null){
            stack.push(cur);
            cur = cur.left;
        }
        cur = stack.poll();
        map.put(cur,cnt);
        cnt++;
        cur = cur.right;
    }
    cur = root;
    while(cur != null){
        if(map.get(cur)< k-1){
            cur = cur.right;
        }
        else if(map.get(cur) > k-1){
            cur = cur.left;
        }
        else{
            break;
        }
    }
    return cur.val;
}

//方法二(看了官方实现版):记录子树的节点数(解决问题:如果你需要频繁地查找第 k 小的值,你将如何优化算法?)
//时间复杂度:O(H+k),H为树的高度,在开始遍历之前,我们需要O(H)到达叶结点
//空间复杂度:O(H),H为树的高度
class Solution {
    public int kthSmallest(TreeNode root, int k) {
        MyBst myBst = new MyBst(root);
        return myBst.kthSmallest(k);
    }
}

class MyBst{
    private TreeNode root;
    private Map<TreeNode,Integer> nodeNum;

    public MyBst(TreeNode root){
        this.root = root;
        this.nodeNum = new HashMap<>();
        countNodeNum(root);
    }

    //计算以每个节点作为根节点的子树节点总数
    private Integer countNodeNum(TreeNode cur){
        if(cur == null){
            return 0;
        }
        nodeNum.put(cur, 1 + countNodeNum(cur.left) + countNodeNum(cur.right));
        return nodeNum.get(cur);
    }

    //获取以当前节点cur为根节点的子树节点总数
    private Integer getNodeNum(TreeNode cur){
        return nodeNum.getOrDefault(cur,0);
    }

    //获取搜索二叉树第k小的元素
    public Integer kthSmallest(int k){
        TreeNode cur = root;
        int leftNum;
        while(cur != null){
            leftNum = getNodeNum(cur.left);
            if(leftNum < k-1){
                cur = cur.right;
                k = k-leftNum-1;
            }
            else if(leftNum > k-1){
                cur = cur.left;
            }
            else{
                break;
            }
        }
        return cur.val;
    }
}

//方法三:平衡二叉搜索树(解决问题:如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第k小的值,你将如何优化算法?)
//时间复杂度:预处理的时间复杂度为 O(N),其中 N 是树中结点的总数。插入、删除和搜索的时间复杂度均为 O(logN)
//空间复杂度:O(N),用于存储平衡二叉搜索树

分析:

​ 1、官方题解方法一的解题思路与我的解答思路一致,只是具体实现略有不同。

​ 2、方法二的解题思路:设计一个新的类MyBst,计算并用哈希表记录二叉树以每个节点作为根节点时的子树节点总数,并实现获取搜索二叉树第k小的元素方法kthSmallest。用root构建MyBst类的对象,直接调用MyBst类对象的kthSmallest方法即可。

​ 在知道二叉树以每个节点作为根节点时的子树节点总数后,如何快速获取搜索二叉树第k小的元素?我们知道每个节点的左子树节点总数leftNum,若leftNum>k-1,那么第k小的元素一定在当前节点的右子树上,我们继续往当前节点的右子树遍历,且k变为k-leftNum-1;若leftNum<k-1,那么第k小的元素一定在当前节点的左子树上,我们继续往当前节点的左子树遍历;若leftNum==k-1,那么当前节点就是第k小的元素。

​ 3、官方题解三需要自己实现平衡二叉搜索树(AVL树),用于解决"二叉搜索树经常被修改(插入/删除操作)并且需要频繁地查找第k小的值"的问题,编码难度很大,暂时略过。

总结

  • 本题主要掌握"搜索二叉树的中序遍历是升序序列"这个性质,根据这个性质即可解题。
  • 对于本题的进阶问题"如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?",暂时只学习了进阶问题的一部分"搜索二叉树不变,但需要频繁地查找第 k 小的值",可利用哈希表维护每个节点作为根节点时子树的节点总数来实现快速查找。
相关推荐
008爬虫实战录17 小时前
【数据结构】 树、二叉树、完全二叉树,先序遍历、中序遍历、后序遍历
数据结构·算法
AllData公司负责人18 小时前
大模型赋能AllData数据中台,系列升级|通过联合智谱大模型与BiSheng开源项目,建设企业大模型应用开发平台,支持知识库向量检索!
大数据·数据结构·数据库·算法·大模型·向量数据库·智谱ai
这料鬼有毒18 小时前
二刷hot100-78.子集
算法·leetcode·职场和发展
梦想的颜色19 小时前
MySQL 数据存储结构与查询执行生命周期深度解析
运维·数据结构·数据库·mysql·线程·优化
Ameilide19 小时前
数据结构 算法解释,排序、查找
数据结构
医用门20 小时前
医院钢制门厂家有哪些品牌好的
leetcode
真实的菜20 小时前
Redis 从入门到精通(二):深入数据结构 —— 从 RedisObject 到 SkipList 的源码级拆解
数据结构·redis·skiplist
mifengxing21 小时前
LeetCode热题100——字母异位词分组
java·算法·leetcode·职场和发展·哈希表·hot100
小欣加油1 天前
leetcode41 缺失的第一个正数
数据结构·c++·算法·leetcode
努力努力再努力wz1 天前
【Qt入门系列】一文掌握 Qt 常用显示类控件:QLCDNumber、QProgressBar 与 QCalendarWidget
c语言·开发语言·数据结构·数据库·c++·git·qt