Loading

10 二叉搜索树

1. 验证二叉搜索树

image
image
image

1.1 解题思路

1.1.1 方法一:前序遍历

根据定义,二叉树的左右子树一定也是二叉搜索树。
因此:
原问题:判断一棵树是否是二叉搜索树
子问题:判断一棵树的左子树/右子树是否是二叉搜索树

在递归时,除了传入节点,还需要传入该节点所处开区间的范围。
对于每个节点,先判断它的节点值是否在开区间内,然后再往下递归。
如果递归到左子结点,那就更新开区间的右边界为节点值;
如果递归到右子节点,就更新开区间的左边界为节点值;

像这种先访问节点值再递归左右子树的做法,叫做前序遍历

1.1.2 方法二:中序遍历

如果按照“左根右”,递归左子树,访问根节点值,再递归右子树,这样的顺序遍历,将会得到一个递增的数组。
遍历完左子树,我们就可以看当前的值是否严格大于上一个节点的值,然后再记录下当前节点的值,用于递归时与下一个节点进行比较。

1.1.3 方法三:后序遍历

把节点值的范围往上传,先遍历左右子树。如果判断当前节点大于左子树的最大值而且小于右子树的最小值,那么认为这是合法的。

看上去左边只需要返回一个最大值,右边只需要返回最小值即可。(最小值和最大值都需要返回)

1.2 代码实现

1.2.1 方法一:前序遍历

点击查看代码
class Solution {
public:
    bool isValidBST(TreeNode* root, long long left = LLONG_MIN, long long right = LLONG_MAX) {
        if (root == nullptr) {
            return true;
        }
        long long val = root->val;
        return val > left && val < right &&
            isValidBST(root->left, left, val) &&
            isValidBST(root->right, val, right);
    }
};

1.2.2 方法二:中序遍历

点击查看代码
class Solution {
    long long pre = LLONG_MIN;
public:
    bool isValidBST(TreeNode* root) {
        if (root == nullptr) {
            return true;
        }
        if (!isValidBST(root->left)) {
            return false;
        }
        if (root->val <= pre) {
            return false;
        }
        pre = root->val;
        return isValidBST(root->right);
    }
};

1.2.3 方法三:后序遍历

点击查看代码
class Solution {
public:
    bool isValidBST(TreeNode* root) {
        long long right = LLONG_MAX;
        long long left = LLONG_MIN;
        auto dfs = [&](this auto&&dfs, TreeNode* root) ->pair<long long, long long> {
            if (root == nullptr) {
                return {right, left};
            }
            auto [l_min, l_max] = dfs(root->left);
            auto [r_min, r_max] = dfs(root->right);
            long long val = root->val;
            if (val <= l_max || val >= r_min) {
                return {left, right};
            } // 因为空节点时返回的是{inf, -inf}
            return {min(l_min, val), max(r_max, val)};
        };
        return dfs(root).second != right;    
    }
};
  • 时间复杂度:$$O(n)$$
  • 空间复杂度:$$O(n)$$

2 二叉树的最近公共祖先

image
image

2.1. 解题思路

给你一棵二叉树,以及这棵树上的两个节点\(p\)\(q\),要怎么找到\(p\)\(q\)的最近公共祖先呢?
递归到当前节点,分类讨论
1.当前节点是空节点:直接返回空节点
2.当前节点是\(p\):不需要继续往下递归了,直接返回\(p\)。原因是:

  • 无论\(q\)\(p\)左子树中的一个节点还是\(p\)右子树中的一个节点,最近公共祖先都是\(p\)
  • 或者说\(p\)在左子树,\(q\)在右子树,这种情况下也不需要继续往下递归

3.当前节点是\(q\):同2
4.其他情况:是否找到\(p\)\(q\)

  • \(p\)\(q\)分别在左右子树中:最近公共祖先就是当前节点,因此返回当前节点
  • \(p\)\(q\)都在左子树中:最近公共祖先一定在左子树中,返回左子树的递归结果
  • \(p\)\(q\)都在右子树中:返回右子树的递归结果
  • 没有找到\(p\)\(q\):返回空

2.2. 代码实现

点击查看代码
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr || root == p || root == q) {
            return root;
        }
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
        if (left != nullptr && right != nullptr) {
            return root;
        } 
        if (left != nullptr)
            return left;
        if (right != nullptr)
            return right;
        return nullptr;
    }
};
  • 时间复杂度:$$O(n)$$
  • 空间复杂度:$$O(n)$$
    小结:一点也不简单

3. 二叉搜索树的最近公共祖先

image
image

3.1. 解题思路

二叉搜索树的性质:左子树的节点值都比根节点值小,右子树的节点值都比根节点值大。
如果\(p\)\(q\)的节点值都小于当前的节点值,那么\(p\)\(q\)(如果有)一定在当前节点的左子树中;(\(p\)\(q\)都在左子树,返回左子树的递归结果)
同理,如果\(p\)\(q\)的节点值都大于当前的节点值,那么\(p\)\(q\)(如果有)一定在当前节点的右子树中。(\(p\)\(q\)都在右子树,返回右子树的递归结果)
其他情况:

  • \(p\)\(q\)分别在左右子树中,那么返回递归的当前节点。
  • 当前节点是\(p\)或者\(q\),直接返回当前节点。

最后,由于题目保证\(p\)\(q\)都在树中,那么根节点一定不是空节点。所以不需要判断空节点。

3.2. 代码实现

点击查看代码
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        int val = root->val;
        if (p->val < val && q->val < val) {
            return lowestCommonAncestor(root->left, p, q);
        } else if (p->val > val && q->val > val) {
            return lowestCommonAncestor(root->right, p, q);
        }
        return root;    
    }
};
  • 时间复杂度:$$O(n)$$
  • 空间复杂度:$$O(n)$$

4. 二叉树的层序遍历

image

4.1 解题思路

4.1.1 法一(双数组):

创建一个初始数组current,把根节点放进去。进入循环,创建一个nxt数组。遍历current数组中的每个节点,将其左右孩子都放入nxt数组中。于是,遍历完current之后,我们就得到了下一层节点的值。

4.1.2 队列:

4.2 代码实现

4.2.1 法一(双数组):

点击查看代码
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (root == nullptr) {
            return {};
        }
        vector<vector<int>> ans;
        vector<TreeNode*> cur;
        vector<TreeNode*> nxt;
        cur.push_back(root);
        while (!cur.empty()) {
            vector<int> temp;
            int size = cur.size();
            for (int i = 0; i < size; ++i) {
                temp.push_back(cur[i]->val);
                if (cur[i]->left != nullptr) {
                    nxt.push_back(cur[i]->left);
                }
                if (cur[i]->right != nullptr) {
                    nxt.push_back(cur[i]->right);
                }
            }
            ans.push_back(std::move(temp));
            cur = std::move(nxt);
        }
        return ans;
    }
};
  • 时间复杂度:$$O(n)$$
  • 空间复杂度:在每一层都填满的情况下,也即满二叉树。第\(1\)\(2^0,那么第\)h$层就有\(2^{h-1}\)个节点,总节点数为\(2^h-1=n\),得到第\(h\)大约最多有\(\frac{n+1}{2}\)个节点。所以空间复杂度为\(O(n)\)

4.2.2 法二:优先队列

点击查看代码
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        // 队列
        if (root == nullptr) {
            return {};
        }
        vector<vector<int>> ans;
        queue<TreeNode*> que;
        que.push(root);
        while (!que.empty()) {
            vector<int> value;
            int size = que.size();
            for (int i = 0; i < size; ++i) {
                auto node = que.front();
                que.pop();
                value.push_back(node->val);
                if (node->left != nullptr) {
                    que.push(node->left);
                }
                if (node->right != nullptr) {
                    que.push(node->right);
                }
            }
            ans.push_back(std::move(value));
        }
        return ans;
    }
};

5 锯齿形层序遍历

image

5.1. 代码实现

点击查看代码
class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
         // 队列
         // 偶数层需要翻转一下数组。因为需要知道当前层是偶数层还是奇数层,所以用一个bool变量
        if (root == nullptr) {
            return {};
        }
        vector<vector<int>> ans;
        queue<TreeNode*> que;
        que.push(root);
        bool even = false; // 一开始是奇数层
        while (!que.empty()) {
            vector<int> value;
            int size = que.size();
            for (int i = 0; i < size; ++i) {
                auto node = que.front();
                que.pop();
                value.push_back(node->val);
                if (node->left != nullptr) {
                    que.push(node->left);
                }
                if (node->right != nullptr) {
                    que.push(node->right);
                }
            }
            if (even) {
                reverse(value.begin(), value.end());
            }
            ans.push_back(std::move(value));
            even = !even;
        }
        return ans;
    }
};

6 找树左下角的值

image
image

6.1 解题思路

依旧是层序遍历的思路。如果按照从左往右的顺序,那么就是记录每层遍历到的第1个节点。如果是从右往左的顺序,那么就是记录每层遍历到的最后1个节点。

6.2 代码实现

点击查看代码
class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        // 队列
        if (root == nullptr) {
            return {};
        }
        int ans = 0;
        queue<TreeNode*> que;
        que.push(root);
        while (!que.empty()) {
			auto node = que.front();
            que.pop();
            ans = node->val;
            if (node->right != nullptr) {
                que.push(node->right);
            }
            if (node->left != nullptr) {
                que.push(node->left);
            }
        }
        return ans;
    }
};
  • 时间复杂度:$$O(n)$$
  • 空间复杂度:$$O(n)$$
posted @ 2026-01-12 09:49  王仲康  阅读(14)  评论(0)    收藏  举报