王清欢Randy 王清欢Randy
首页
  • 编程语言

    • C/C++ 学习笔记
    • Golang 学习笔记
  • 算法分析

    • LeetCode 刷题笔记
  • 操作系统

    • Linux 基础
    • Vim 实用技巧
    • Shell 脚本编程
    • GDB 学习笔记
  • 开发工具

    • Git 学习笔记
  • 分布式理论

    • 共识算法
    • 分布式事务
  • 数据库内核

    • PostgreSQL
    • Postgres-XL
  • hidb
  • pgproxy
  • 实用技巧
  • 学习方法
  • 资源分享
GitHub (opens new window)
首页
  • 编程语言

    • C/C++ 学习笔记
    • Golang 学习笔记
  • 算法分析

    • LeetCode 刷题笔记
  • 操作系统

    • Linux 基础
    • Vim 实用技巧
    • Shell 脚本编程
    • GDB 学习笔记
  • 开发工具

    • Git 学习笔记
  • 分布式理论

    • 共识算法
    • 分布式事务
  • 数据库内核

    • PostgreSQL
    • Postgres-XL
  • hidb
  • pgproxy
  • 实用技巧
  • 学习方法
  • 资源分享
GitHub (opens new window)
  • 基础算法

    • 双指针

      • 双指针基础
      • 碰撞指针
      • 快慢指针
      • 滑动窗口
    • 二分查找

      • 基础应用
      • 边界收缩
      • 局部有序
    • 排序算法

      • 01八大排序
      • 02快速排序
      • 03归并排序
      • 04桶排序
      • 05堆排序
    • 优先搜索

      • 01递归
      • 02网格结构深度优先搜索
      • 03树结构深度优先搜索
      • 04图结构深度优先搜索
      • 05网格结构广度优先搜索
      • 06树结构广度优先搜索
      • 07图结构广度优先搜索
    • 回溯算法

      • 01递归
      • 02 子集问题
      • 03 全排列问题
      • 04 组合问题
      • 05 回溯搜索问题
  • 基础数据结构

    • 线性表与哈希表

      • 01数组
      • 02栈和队列
      • 03单调栈
      • 04优先队列
      • 05双端队列
      • 06哈希表
      • 07多重集合
      • 08前缀和
      • 09数据结构设计
    • 字符串

      • 01字符串比较
      • 02回文字符串
      • 03字符串匹配
      • 04字符串算术表达式
    • 单链表

      • 01链表基础操作
      • 02链表遍历
    • 二叉树

      • 01二叉树的属性
      • 02二叉树的操作
      • 03层次遍历
      • 04前中后序遍历
      • 05二叉搜索树的属性
      • 06二叉搜索树的操作
        • 06 二叉搜索树的操作
          • 669 修剪二叉搜索树
          • 99 恢复二叉搜索树
          • 109 有序链表转换二叉搜索树
          • 450 删除二叉搜索树中的节点
      • 07字典树
      • 08二叉搜索树BST
    • 图

      • 01二分图
      • 02拓扑排序
      • 03并查集
      • 04最小生成树
      • 05最短路径
  • 进阶算法

    • 贪心算法

      • 01跳跃游戏
      • 02分配问题
      • 03区间问题
    • 分治策略

    • 动态规划

      • 01一维动态规划
      • 02二维动态规划
      • 03分割型动态规划
      • 04子序列问题
      • 05背包问题
      • 06字符串编辑问题
      • 07股票交易问题
  • 其他内容

    • 数学问题

      • 01公倍数与公因数
      • 02质数问题
      • 03进制转换问题
      • 04数字字符串求和问题
      • 05众数问题
      • 06中位数问题
      • 07数字处理问题
      • 08随机数问题
    • 位运算

      • 01位运算基础
      • 02妙用异或运算
      • 03二进制特性
  • LeetCode刷题笔记
  • 基础数据结构
  • 二叉树
王清欢
2023-03-24
目录

06二叉搜索树的操作

# 06 二叉搜索树的操作

# 669 修剪二叉搜索树 (opens new window)

​ 给定一个二叉查找树和两个整数 L 和 R,且 L < R,试修剪此二叉查找树,使得修剪后所有节点的值都在 [L, R] 的范围内。

​ 输入是一个二叉查找树和两个整数 L 和 R,输出一个被修剪好的二叉查找树。

输入:root = [3,0,4,null,2,null,null,1], low = 1, high = 3
输出:[3,2,null,1]

解析:

​ 利用二叉查找树的大小关系,我们可以很容易地利用递归进行树的处理。

​ 递归遍历每一个节点,如果当前值大于 high 那么修剪后的二叉树必定出现在节点的左边;如果当前值小于 low 那么修剪后的二叉树出现在节点的右边;如果当前节点值在区间内,则往左右子树递归。

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if(!root){
            return root;
        }
        // 当前值大于 high 那么修剪后的二叉树必定出现在节点的左边
        if(root->val > high){
            return trimBST(root->left,low,high);
        }
        // 当前值小于 low 那么修剪后的二叉树出现在节点的右边
        if(root->val < low){
            return trimBST(root->right,low,high);
        }
        // 当前值在区间内,则往左右子树递归
        root->left = trimBST(root->left,low,high);
        root->right = trimBST(root->right,low,high);
        return root;
    }
};

# 99 恢复二叉搜索树 (opens new window)

给定一个二叉搜索树,已知有两个节点被不小心交换了,试复原此树

输入是一个被误交换两个节点的二叉搜索树,输出是改正后的二叉搜索树

输入:root = [3,1,4,null,null,2] 输出:[2,1,4,null,null,3] 解释:2 不能在 3 的右子树中,因为 2 < 3 。交换 2 和 3 使二叉搜索树有效。

解析:

本题没搞懂,有待深究

​ 使用中序遍历一棵二叉搜索树,得到的遍历结果是节点值递增的序列,利用二叉搜索树这一特点可以快速找出错误的节点。

​ 得到二叉搜索树的中序遍历序列,找到错误的节点,交换两个节点的值。

class Solution {
public:
    void inorder(TreeNode* root, vector<int>& nums) {
        if (root == nullptr) {
            return;
        }
        inorder(root->left, nums);
        nums.push_back(root->val);
        inorder(root->right, nums);
    }

    pair<int,int> findTwoSwapped(vector<int>& nums) {
        int n = nums.size();
        int index1 = -1, index2 = -1;
        for (int i = 0; i < n - 1; ++i) {
            if (nums[i + 1] < nums[i]) {
                index2 = i + 1;
                if (index1 == -1) {
                    index1 = i;
                } else {
                    break;
                }
            }
        }
        int x = nums[index1], y = nums[index2];
        return {x, y};
    }
    
    void recover(TreeNode* r, int count, int x, int y) {
        if (r != nullptr) {
            if (r->val == x || r->val == y) {
                r->val = r->val == x ? y : x;
                if (--count == 0) {
                    return;
                }
            }
            recover(r->left, count, x, y);
            recover(r->right, count, x, y);
        }
    }

    void recoverTree(TreeNode* root) {
        vector<int> nums;
        inorder(root, nums);
        pair<int,int> swapped= findTwoSwapped(nums);
        recover(root, 2, swapped.first, swapped.second);
    }
};

# 109 有序链表转换二叉搜索树 (opens new window)

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

输入一个链表,输出一个左右子树高度差不超过1的平衡二叉搜索树

给定的有序链表: [-10, -3, 0, 5, 9],

一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:

      0
     / \
   -3   9
   /   /
 -10  5

解析:

​ 要保证平衡,那么需要寻找链表的中点,以中点为根节点构造二叉树,小于中点的元素组成左子树,大于中点的元素组成右子树,它们分别对应着有序链表中连续的一段,这就保证了左右子树节点数目之差不超过1,从而实现二叉树的平衡。

​ 在这之后,我们使用分治的思想,继续递归地对左右子树进行构造,找出对应的中点作为根节点,直到构造叶子节点完成时返回。

​ 寻找链表中点的一种常见方式就是快慢指针,快指针和慢指针在同一节点出发,快指针一次走两步,慢指针一次走一步,当快指针到达终点时,慢指针指向的就是链表中点。注意循环条件,本题在递归过程中,子树对应的链表最后一个节点的next不一定是nullptr。

​ 找到链表中点之后,构造该节点,并递归构造其左右节点。

class Solution {
public:

    ListNode* getMid(ListNode* left, ListNode* right){
        ListNode* fast = left;
        ListNode* slow = left;
        // 注意判断条件,终止条件是右边接而不是 nullptr
        while(fast != right && fast->next != right){
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }

    TreeNode* buildTree(ListNode* left, ListNode* right){
        if(left == right){
            return nullptr;
        }
        ListNode* midNode = getMid(left,right);
        TreeNode* root = new TreeNode(midNode->val);
        root->left = buildTree(left,midNode);
        root->right = buildTree(midNode->next,right);
        return root;
    }

    TreeNode* sortedListToBST(ListNode* head) {
        // 链表的右边界是nullptr
        return buildTree(head,nullptr);
    }
};

​ 每次构造节点都需要使用快慢指针遍历链表寻找中点,这极大地耗损了算法效率。由于构造出的二叉搜索树的中序遍历结果就是链表本身,我们可以采用中序遍历思路减少时间复杂度。

​ 中序遍历的顺序是「左子树 - 根节点 - 右子树」,那么在分治的过程中,我们不用急着找出链表的中位数节点,而是使用一个占位节点,等到中序遍历到该节点时,再填充它的值。

​ 在中序序列中,假设左端点编号为left,右端点编号为right,那么根节点就是 mid = (left+right+1)/2 或者是 mid = (left+right)/2 ,左子树节点范围为 [left, mid-1],右子树节点范围为 [mid+1,right]。根据中序遍历结果恢复二叉搜索树。

class Solution {
public:
    TreeNode* buildTreeMid(ListNode*& head, int left, int right){
        if(left > right){
            return nullptr;
        }
        int mid = (left+right+1)/2;
        TreeNode* root = new TreeNode();
        root->left = buildTreeMid(head,left,mid-1);
        root->val = head->val;
        head = head->next; // 不懂为什么要传引用,而不可以直接传实参
        root->right = buildTreeMid(head,mid+1,right);
        return root;
    }

    TreeNode* sortedListToBST(ListNode* head) {
        int len = 0;
        ListNode* cur = head;
        while(cur){
            ++len;
            cur = cur->next;
        }
        return buildTreeMid(head,0,len-1);
    }
};

# 450 删除二叉搜索树中的节点 (opens new window)

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。

输入一个二叉搜索树和待删除节点值 key,输入一个删除节点key后的二叉搜索树

输入:root = [5,3,6,2,4,null,7], key = 3 输出:[5,4,6,2,null,null,7] 解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。一个正确的答案是 [5,4,6,2,null,null,7]

解析:

​ 1. 找到需要删除的节点

​ 利用二叉搜索树特性,递归查找待删除节点。如果当前节点值小于 key 那么往其右子树递归查找,如果当前节点值大于 key 那么往其左子树递归查找,如果相等就开始删除操作。

​ 2. 删除的节点

​ 删除节点要区分四种情况:当前节点是叶节点、只有左子节点、只有右子节点和有两个子节点

  • 当前节点是叶节点:直接删除节点, 返回NULL为根节点、
  • 当前节点只有左子节点:删除节点,左孩子补位,返回左孩子为根节点
  • 当前节点只有右子节点:删除节点,右孩子补位,返回右孩子为根节点
  • 当前节点有两个子节点:将当前节点的左子树整体移动到其右子树的最左侧节点的左节点上,返回删除节点右节点为新的根节点
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if(!root){
            return root;
        }
        if(root->val == key){
            // 叶子节点
            if(!root->left && !root->right){
                delete root;
                return nullptr;
            }
            // 只有左节点
            else if(root->left && !root->right){
                TreeNode* node = root->left;
                delete root;
                return node;
            }
            // 只有右节点
            else if(!root->left && root->right){
                TreeNode* node = root->right;
                delete root;
                return node;
            }
            // 有两个子节点
            else{
                TreeNode* cur = root->right;
                while(cur->left){
                    cur = cur->left;
                }
                cur->left = root->left;
                TreeNode* node = root->right;
                delete root;
                return node;
            }
        }
        // 递归查找删除节点
        if(root->val > key){
            root->left = deleteNode(root->left,key);
        }
        if(root->val < key){
            root->right = deleteNode(root->right,key);
        }
        return root;
    }
};
上次更新: 2023/11/19, 12:55:48
05二叉搜索树的属性
07字典树

← 05二叉搜索树的属性 07字典树→

Theme by Vdoing | Copyright © 2023-2024 Wang Qinghuan | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式