LeetCode 热题 100—— 二叉搜索树中第 K 小的元素(二叉树)+ 二叉树的右视图(二叉树)

目录

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

99. 二叉树的右视图 中等


中等

给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 小的元素(从 1 开始计数)。

示例 1:

LeetCode 热题 100—— 二叉搜索树中第 K 小的元素(二叉树)+ 二叉树的右视图(二叉树)_第1张图片

输入:root = [3,1,4,null,2], k = 1
输出:1

示例 2:

LeetCode 热题 100—— 二叉搜索树中第 K 小的元素(二叉树)+ 二叉树的右视图(二叉树)_第2张图片

输入:root = [5,3,6,2,4,null,null,1], k = 3
输出:3

提示:

  • 树中的节点数为 n

  • 1 <= k <= n <= 104

  • 0 <= Node.val <= 104

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

//方法一:中序遍历
​
//因为二叉搜索树和中序遍历的性质,所以二叉搜索树的中序遍历是按照键增加的顺序进行的。于是,我们可以通过中序遍历找到第 k 个最小元素。
//用迭代方法,这样可以在找到答案后停止,不需要遍历整棵树。
​
//时间复杂度:O(H+k),其中 H 是树的高度。在开始遍历之前,我们需要 O(H) 到达叶结点。当树是平衡树时,时间复杂度取得最小值 O(logN+k);当树是线性树(树中每个结点都只有一个子结点或没有子结点)时,时间复杂度取得最大值 O(N+k)。
​
//空间复杂度:O(H),栈中最多需要存储 H 个元素。当树是平衡树时,空间复杂度取得最小值 O(logN);当树是线性树时,空间复杂度取得最大值 O(N)。
​
class Solution {
public:
    int kthSmallest(TreeNode* root, int k) {
        // 使用栈辅助中序遍历
        stack stack;
        // 外层循环:遍历所有节点
        while (root != nullptr || stack.size() > 0) {
            // 内层循环:将当前节点及其所有左子节点压入栈
            while (root != nullptr) {
                stack.push(root);
                root = root->left;
            }
            
            // 取出栈顶节点(当前最小节点)
            root = stack.top();
            stack.pop();
            // 计数器减1
            --k;
            // 找到第k小的节点
            if (k == 0) {
                break;
            }
            
            // 转向右子树,继续中序遍历
            root = root->right;
        }
        // 返回第k小的节点值
        return root->val;
    }
};

//方法二:记录子树的结点数
​
/*
在方法一中,我们之所以需要中序遍历前 k 个元素,是因为我们不知道子树的结点数量,不得不通过遍历子树的方式来获知。因此,我们可以记录下以每个结点为根结点的子树的结点数,并在查找第 k 小的值时,使用如下方法搜索:
​
- 令 node 等于根结点,开始搜索。
- 对当前结点 node 进行如下操作:
1. 如果 node 的左子树的结点数 left 小于 k−1,则第 k 小的元素一定在 node 的右子树中,令 node 等于其的右子结点,k 等于 k−left−1,并继续搜索;
2. 如果 node 的左子树的结点数 left 等于 k−1,则第 k 小的元素即为 node ,结束搜索并返回 node 即可;
3. 如果 node 的左子树的结点数 left 大于 k−1,则第 k 小的元素一定在 node 的左子树中,令 node 等于其左子结点,并继续搜索。
- 在实现中,我们既可以将以每个结点为根结点的子树的结点数存储在结点中,也可以将其记录在哈希表中。
*/
​
//时间复杂度:预处理的时间复杂度为 O(N),其中 N 是树中结点的总数;我们需要遍历树中所有结点来统计以每个结点为根结点的子树的结点数。搜索的时间复杂度为 O(H),其中 H 是树的高度;当树是平衡树时,时间复杂度取得最小值 O(logN);当树是线性树时,时间复杂度取得最大值 O(N)。
//空间复杂度:O(N),用于存储以每个结点为根结点的子树的结点数。
​
​
class MyBst {
public:
    // 构造函数:初始化根节点并统计所有子树节点数
    MyBst(TreeNode *root) {
        this->root = root;
        countNodeNum(root);
    }
​
    // 返回二叉搜索树中第k小的元素
    int kthSmallest(int k) {
        TreeNode *node = root;
        while (node != nullptr) {
            int left = getNodeNum(node->left);
             // 目标在右子树
            if (left < k - 1) {
                node = node->right;
                k -= left + 1;
            } 
            // 当前节点即为目标
            else if (left == k - 1) {
                break;
            } 
            // 目标在左子树
            else {
                node = node->left;
            }
        }
        return node->val;
    }
​
private:
    // 根节点指针
    TreeNode *root;
    // 存储节点及其子树大小
    unordered_map nodeNum;
​
    // 函数通过后序遍历递归计算每个节点的子树大小,结果存入 nodeNum 哈希表。
    int countNodeNum(TreeNode * node) {
        if (node == nullptr) {
            return 0;
        }
        nodeNum[node] = 1 + countNodeNum(node->left) + countNodeNum(node->right);
        return nodeNum[node];
    }
​
    // 获取以node为根结点的子树的结点数(从哈希表中查询)
    int getNodeNum(TreeNode * node) {
        //若 node 非空且在 nodeNum 中存在记录,直接返回存储的节点数。
        if (node != nullptr && nodeNum.count(node)) {
            return nodeNum[node];
        }else{
            return 0;
        }
    }
};
​
class Solution {
public:
    int kthSmallest(TreeNode* root, int k) {
        MyBst bst(root);
        return bst.kthSmallest(k);
    }
};

99. 二叉树的右视图 中等

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例 1:

输入:root = [1,2,3,null,5,null,4]

输出:[1,3,4]

解释:

LeetCode 热题 100—— 二叉搜索树中第 K 小的元素(二叉树)+ 二叉树的右视图(二叉树)_第3张图片

示例 2:

输入:root = [1,2,3,4,null,null,null,5]

输出:[1,3,4,5]

解释:

LeetCode 热题 100—— 二叉搜索树中第 K 小的元素(二叉树)+ 二叉树的右视图(二叉树)_第4张图片

示例 3:

输入:root = [1,null,3]

输出:[1,3]

示例 4:

输入:root = []

输出:[]

提示:

  • 二叉树的节点个数的范围是 [0,100]

  • -100 <= Node.val <= 100

//一、BFS
//思路: 利用 BFS 进行层次遍历,记录下每层的最后一个元素。
//时间复杂度: O(N),每个节点都入队出队了 1 次。
//空间复杂度: O(N),使用了额外的队列空间。
​
class Solution {
public:
    vector rightSideView(TreeNode* root) {
        // 存储右视图结果的向量
        vector res;
        // 处理空树的情况
        if (root == nullptr) {
            return res;
        }
        
         // 使用队列进行BFS
        queue q;
        // 初始化队列,加入根节点
        q.push(root);
        
        // 循环处理每一层
        while (!q.empty()) {
             // 当前层的节点数量
            int size = q.size();
            
            // 遍历当前层的所有节点
            for (int i = 0; i < size; i++) {
                // 取出队首节点
                TreeNode* node = q.front();
                q.pop();
                
                // 将当前节点的左右子节点(若存在)加入队列
                if (node->left) {
                    q.push(node->left);
                }
                if (node->right) {
                    q.push(node->right);
                }
                
                 // 当遍历到当前层的最后一个节点时
                if (i == size - 1) {
                    res.push_back(node->val);
                }
            }
        }
        // 返回右视图结果
        return res;
    }
};

//二、DFS (时间100%)
//思路: 我们按照 「根结点 -> 右子树 -> 左子树」 的顺序访问,就可以保证每层都是最先访问最右边的节点的。
​
//(与先序遍历 「根结点 -> 左子树 -> 右子树」 正好相反,先序遍历每层最先访问的是最左边的节点)
​
//时间复杂度: O(N),每个节点都访问了 1 次。
//空间复杂度: O(N),因为这不是一棵平衡二叉树,二叉树的深度最少是 logN, 最坏的情况下会退化成一条链表,深度就是 N,因此递归时使用的栈空间是 O(N) 的。
​
class Solution {
private:
    // 存储右视图结果的数组
    vector res;
​
    // 深度优先搜索函数
    void dfs(TreeNode* root, int depth) {
        // 处理空节点
        if (root == nullptr) {
            return;
        }
        
        // 如果当前深度等于结果数组的大小,说明是该深度下第一个访问的节点(即最右侧节点)
        if (depth == res.size()) {
            res.push_back(root->val);
        }
        
        // 先递归处理右子树,再处理左子树
        dfs(root->right, depth + 1);
        dfs(root->left, depth + 1);
    }
​
public:
    vector rightSideView(TreeNode* root) {
        dfs(root, 0); // 从根节点开始,深度为0
        return res;
    }
};

你可能感兴趣的:(LeetCode,热题,100,leetcode,算法,职场和发展)