二叉树:我是不是一棵二叉搜索树

Posted 代码随想录

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树:我是不是一棵二叉搜索树相关的知识,希望对你有一定的参考价值。

给「代码随想录」一个星标吧!

学习完二叉搜索树的特性了,那么就验证一波

98.验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

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


二叉树:我是不是一棵二叉搜索树

思路

要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。

有了这个特性,「验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。」

递归法

可以递归中序遍历将二叉搜索树转变成一个数组,代码如下:

vector<int> vec;
void traversal(TreeNode* root) {
    if (root == NULL) return;
    traversal(root->left);
    vec.push_back(root->val); // 将二叉搜索树转换为有序数组
    traversal(root->right);
}

然后只要比较一下,这个数组是否是有序的,「注意二叉搜索树中不能有重复元素」。

traversal(root);
for (int i = 1; i < vec.size(); i++) {
    // 注意要小于等于,搜索树里不能有相同元素
    if (vec[i] <= vec[i - 1]) return false;
}
return true;

整体代码如下:

class Solution {
private:
    vector<int> vec;
    void traversal(TreeNode* root) {
        if (root == NULL) return;
        traversal(root->left);
        vec.push_back(root->val); // 将二叉搜索树转换为有序数组
        traversal(root->right);
    }
public:
    bool isValidBST(TreeNode* root) {
        vec.clear(); // 不加这句在leetcode上也可以过,但最好加上
        traversal(root);
        for (int i = 1; i < vec.size(); i++) {
            // 注意要小于等于,搜索树里不能有相同元素
            if (vec[i] <= vec[i - 1]) return false;
        }
        return true;
    }
};

以上代码中,我们把二叉树转变为数组来判断,是最直观的,但其实不用转变成数组,可以在递归遍历的过程中直接判断是否有序。

这道题目比较容易陷入两个陷阱:

  • 陷阱1

「不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了」。

写出了类似这样的代码:

if (root->val > root->left->val && root->val < root->right->val) {
    return true;
else {
    return false;
}

我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。所以以上代码的判断逻辑是错误的。

例如:[10,5,15,null,null,6,20] 这个case:

二叉树:我是不是一棵二叉搜索树

节点10小于左节点5,大于右节点15,但右子树里出现了一个6 这就不符合了!

  • 陷阱2

样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的。

此时可以初始化比较元素为longlong的最小值。

问题可以进一步演进:如果样例中根节点的val 可能是longlong的最小值 又要怎么办呢?文中会解答。

了解这些陷阱之后我们来看一下代码应该怎么写:

递归三部曲:

  • 确定递归函数,返回值以及参数

要定义一个longlong的全局变量,用来比较遍历的节点是否有序,因为后台测试数据中有int最小值,所以定义为longlong的类型,初始化为longlong最小值。

注意递归函数要有bool类型的返回值, 我们在 中讲了,只有寻找某一条边(或者一个节点)的时候,递归函数会有bool类型的返回值。

其实本题是同样的道理,我们在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,立刻返回。

代码如下:

long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
bool isValidBST(TreeNode* root) 
  • 确定终止条件

如果是空节点 是不是二叉搜索树呢?

是的,二叉搜索树也可以为空!

代码如下:

if (root == NULL) return true;
  • 确定单层递归的逻辑

中序遍历,一直更新maxVal,一旦发现maxVal >= root->val,就返回false,注意元素相同时候也要返回false。

代码如下:

bool left = isValidBST(root->left);         // 左

// 中序遍历,验证遍历的元素是不是从小到大
if (maxVal < root->val) maxVal = root->val; // 中
else return false;

bool right = isValidBST(root->right);       // 右
return left && right;

整体代码如下:

class Solution {
public:
    long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
    bool isValidBST(TreeNode* root) {
        if (root == NULL) return true;

        bool left = isValidBST(root->left);
        // 中序遍历,验证遍历的元素是不是从小到大
        if (maxVal < root->val) maxVal = root->val;
        else return false;
        bool right = isValidBST(root->right);

        return left && right;
    }
};

以上代码是因为后台数据有int最小值测试用例,所以都把maxVal改成了longlong最小值。

如果测试数据中有 longlong的最小值,怎么办?

不可能在初始化一个更小的值了吧。建议避免 初始化最小值,如下方法取到最左面节点的数值来比较。

代码如下:

class Solution {
public:
    TreeNode* pre = NULL; // 用来记录前一个节点 
    bool isValidBST(TreeNode* root) {
        if (root == NULL) return true;
        bool left = isValidBST(root->left);

        if (pre != NULL && pre->val >= root->val) return false;
        pre = root; // 记录前一个节点

        bool right = isValidBST(root->right);
        return left && right;
    }
};

最后这份代码看上去整洁一些,思路也清晰。

迭代法

可以用迭代法模拟二叉树中序遍历,对前中后序迭代法生疏的同学可以看这两篇,

迭代法中序遍历稍加改动就可以了,代码如下:

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        stack<TreeNode*> st;
        TreeNode* cur = root;
        TreeNode* pre = NULL; // 记录前一个节点
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) {
                st.push(cur);
                cur = cur->left;                // 左
            } else {
                cur = st.top();                 // 中
                st.pop();
                if (pre != NULL && cur->val <= pre->val)
                return false;
                pre = cur; //保存前一个访问的结点

                cur = cur->right;               // 右
            }
        }
        return true;
    }
};

总结

这道题目是一个简单题,但对于没接触过的同学还是有难度的。

所以初学者刚开始学习算法的时候,看到简单题目没有思路很正常,千万别怀疑自己智商,学习过程都是这样的,大家智商都差不多,哈哈。

只要把基本类型的题目都做过,总结过之后,思路自然就开阔了。

「就酱,学到了的话,就转发给身边需要的同学吧!」

在留言区留下你的思路吧!

-------end-------

我将算法学习相关的资料已经整理到了Github :https://github.com/youngyangyang04/leetcode-master,里面还有leetcode刷题攻略、各个类型经典题目刷题顺序、思维导图看一看一定会有所收获,如果给你有帮助给一个star支持一下吧!

另外因为公众号改版,时间线被打乱,一些精彩文章大家可能错过了。如果感觉这里的文章对你有帮助, 赶紧给「代码随想录」加一个星标吧,方便第一时间阅读文章

往期 精彩回顾
























「代码随想录」期待你的关注!

每天8:35准时推送一道经典算法题目,推送的每道题目都不是孤立的,而是由浅入深,环环相扣,帮你梳理算法知识脉络,轻松学算法!

二叉树:我是不是一棵二叉搜索树
刷题可以加我微信!
右边为个人微信,添加时备注:简单自我介绍」+组队题」
我就知道你[在看]

以上是关于二叉树:我是不是一棵二叉搜索树的主要内容,如果未能解决你的问题,请参考以下文章

NC60 判断一棵二叉树是否为搜索二叉树和完全二叉树

二叉树和二叉搜索树

二叉树进阶之寻找一棵二叉树中的最大二叉搜索子树

判断一棵二叉树是否为二叉排序树

二叉搜索树

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表