11 二叉搜索树

222 阅读5分钟

灵神【基础算法精讲】视频的个人笔记。

一、视频例题

验证二叉搜索树

二叉搜索树:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

1.1 先序遍历

先判断,再递归。 维护一个区间,结点的值必须在区间内,才是二叉搜索树。

bool preOrder(TreeNode* node, long left, long right) {
    if(!node) return true;
    
    return node->val > left && node->val < right
        && preOrder(node->left, left, node->val) //左孩子小于当前节点
        && preOrder(node->right, node->val, right); //右孩子大于当前节点
}

bool isValidBST(TreeNode* root) {
    return preOrder(root, LONG_MIN, LONG_MAX);
}

1.2 中序遍历

大于上一个节点。用一个全局变量pre来保存上一个节点的值。

class Solution {
public:
    long pre = LONG_MIN;
    bool inOrder(TreeNode* node) {
        if(!node) return true;

        if(! inOrder(node->left)) return false;

        if(node->val <= pre) return false;
        pre = node->val;
        
        return inOrder(node->right);
    }

    bool isValidBST(TreeNode* root) {
        return inOrder(root);
    }
};

if(! inOrder(node->left)) return false;

  • 如果左子不成立,该子树不是二叉搜索树,整棵树就不是二叉搜索树。
  • 一个条件不成立,能推出整棵树不是二叉搜索树。

这里不能写if(inOrder(node->left)) return true;

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

只有左子成立,满足了第一个条件,下面两条未知,所以不能推出该子树是二叉搜索树。


1.3 后序遍历

先递归,再判断 节点的值往上传 image.png

class Solution {
public:
    pair<long, long> postOrder(TreeNode* node) {
        if(!node) return {LONG_MAX, LONG_MIN};

        pair<long, long> left = postOrder(node->left);
        pair<long, long> right = postOrder(node->right);

        long v = node->val;
        long l_min = left.first, l_max = left.second;
        long r_min  = right.first, r_max = right.second;
        if(v <= l_max || v >= r_min)
            return {LONG_MIN,  LONG_MAX};
        return { min(l_min, v), max(r_max, v) }; //子树的最小值和最大值
    }

    bool isValidBST(TreeNode* root) {
        return postOrder(root).second != LONG_MAX;
    }
};

课后作业:

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

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

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

示例 1:

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

中序遍历,就是一个排好序的列表

class Solution {
public:
    int count, ans;
    void dfs(TreeNode* node) {
        if(!node || count <= 0) return;

        dfs(node->left);
        if(--count == 0) ans = node->val;
        dfs(node->right);
    }

    int kthSmallest(TreeNode* root, int k) {
        count = k;
        dfs(root);
        return ans;
    }
};

501 二叉搜索树中的众数

二叉搜索树中的众数

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。 如果树中有不止一个众数,可以按 任意顺序 返回。 假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

示例 1:

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

中序遍历,就是一个排好序的列表,只需要维护这三个变量。

  • 当前数
  • 当前数的出现次数
  • 最大出现次数
class Solution {
public:
    int v, cnt = 0, maxCnt = 0;
    vector<int> ans;

    void find(TreeNode* node) {
        if(!node) return;

        find(node->left);

        if(node->val == v) { //当前计算的数不变
            cnt++;
        } else { //改变
            v = node->val;
            cnt = 1;
        }

        if(cnt > maxCnt) { //超过最大次数
            maxCnt = max(maxCnt, cnt); //更新最大次数
            ans = vector<int>{ node->val }; //重置
        } else if(cnt == maxCnt) { //一样, 加入ans
            ans.emplace_back(node->val);
        }

        find(node->right);
    }

    vector<int> findMode(TreeNode* root) {
        v = root->val;
        ans = vector<int>{root->val};
        find(root);
        return ans;
    }
};

530 二叉搜索树的最小绝对差

力扣

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数,其数值等于两值之差的绝对值。

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

这题跟上一题差不多,也是利用排好序的性质。

最小绝对差肯定在相邻的节点中产生,维护两个变量:

  • 最小绝对差
  • 上一个节点的值
class Solution {
public:
    int diff = INT_MAX, pre = -1;

    void find(TreeNode* node) {
        if(!node) return;

        find(node->left);

        if(pre == -1) //pre未用过
            pre = node->val;

        if(node->val != pre) { //与上一个值不同, 若相同, 差值为0
            diff = min(diff, node->val - pre);
            pre = node->val;
        }

        find(node->right);
    }

    int getMinimumDifference(TreeNode* root) {
        find(root);
        return diff;
    }
};

700 二叉搜索树中的搜索

力扣

给定二叉搜索树(BST)的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。

示例 1:

输入:root = [4,2,7,1,3], val = 2 
    输出:[2,1,3]

利用性质,左边小,右边大。

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        if(!root) return nullptr;
        
        if(root->val == val)
            return root;
        else if(root->val < val)
            return searchBST(root->right, val);
        else
            return searchBST(root->left, val);
    }
};

1373 二叉搜索子树的最大键值和

力扣

给你一棵以 root 为根的 二叉树 ,请你返回 任意 二叉搜索子树的最大键值和。

二叉搜索树的定义如下:

任意节点的左子树中的键值都 小于 此节点的键值。 任意节点的右子树中的键值都 大于 此节点的键值。 任意节点的左子树和右子树都是二叉搜索树。

示例 1: image.png

输入:root = [1,4,3,2,4,2,5,null,null,null,null,null,null,4,6]
输出:20
解释:键值为 3 的子树是和最大的二叉搜索树。

跟视频中讲到的后序遍历,基本一致,多了个最大子树和。

class Solution {
public:
    int ans = 0;

    tuple<int, int, int> dfs(TreeNode* node) {
        if(!node) return {INT_MAX, INT_MIN, 0};

        auto [l_min, l_max, l_sum] = dfs(node->left);
        auto [r_min, r_max, r_sum] = dfs(node->right);

        int v = node->val;
        if(v <= l_max || v >= r_min) 
            return {INT_MIN, INT_MAX, 0};

        int sum = l_sum + r_sum + v; //子树和
        ans = max(ans, sum);
        return { min(v, l_min), max(v, r_max), sum };
    }

    int maxSumBST(TreeNode* root) {
        dfs(root);
        return ans;
    }
};