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 小的值",可利用哈希表维护每个节点作为根节点时子树的节点总数来实现快速查找。
相关推荐
To_OC16 小时前
LC 200 岛屿数量:经典 DFS 入门题,我第一次写居然连方向都搞错了
javascript·算法·leetcode
To_OC1 天前
LC 128 最长连续序列:别上来就排序,O (n) 解法才是这题的灵魂
javascript·算法·leetcode
刘马想放假2 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠3 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
To_OC3 天前
LC 49 字母异位词分组:想到哈希表很简单,选对 key 才是精髓
javascript·算法·leetcode
To_OC5 天前
LC 1 两数之和:面试第一道必考题,暴力解法直接被面试官 pass
javascript·算法·leetcode
Darling噜啦啦10 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠11 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾11 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
Qres82111 天前
算法复键——树状数组
数据结构·算法