算法学习Day21二叉搜索树、公共祖先

发布时间 2024-01-03 20:20:06作者: HQWQF

Day21二叉搜索树、公共祖先

By HQWQF 2024/01/03

笔记


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

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值

差值是一个正数,其数值等于两值之差的绝对值。

示例 1:

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

递归法

利用二叉搜索树的性质,通过中序遍历的方式将二叉搜索树当作有序的数组来看待。

求有序的数组中任意元素值之间的最小差值就很简单了。

递归法代码

class Solution {
private:
int result = INT_MAX;
TreeNode* pre = NULL;
void traversal(TreeNode* cur) {
    if (cur == NULL) return;
    traversal(cur->left);   // 左
    if (pre != NULL){       // 中
        result = min(result, cur->val - pre->val);
    }
    pre = cur; // 记录前一个
    traversal(cur->right);  // 右
}
public:
    int getMinimumDifference(TreeNode* root) {
        traversal(root);
        return result;
    }
};

迭代法

迭代法代码

class Solution {
public:
    int getMinimumDifference(TreeNode* root) {
        stack<TreeNode*> st;
        TreeNode* cur = root;
        TreeNode* pre = NULL;
        int result = INT_MAX;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指针来访问节点,访问到最底层
                st.push(cur); // 将访问的节点放进栈
                cur = cur->left;                // 左
            } else {
                cur = st.top();
                st.pop();
                if (pre != NULL) {              // 中
                    result = min(result, cur->val - pre->val);
                }
                pre = cur;
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

501.二叉搜索树中的众数

给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

假定 BST 有如下定义:

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

例如:

给定 BST [1,null,2,2],

返回[2].

递归法

由于中序遍历可以将二叉搜索树当作有序的数组来看待,而在有序的数组里,所有值相等的元素都是连续在一起的,所以我们可以用指向上一个节点的指针来判断当前元素和上一个元素的值是否相等,相等就累加一个变量来统计某一个元素出现的频次,这样我们就能统计从出现的最大频次。

但是题目还要求返回所有众数,我们可以在得出最大频次后再遍历一次树,将所有频次等于最大频次的元素都加到返回数组中。

但是,其实我们也可以在一次遍历中就得到结果,只要我们在遍历过程中,只要当前统计到的某个元素的频次等于当前最大频次,我们就把这个元素加入到结果数组中,但是,如果统计到的某个元素的频次大于当前最大频次,我们就先将结果数组清空再加入这个元素。

这样虽然操作数组的次数多了,但是只需要遍历一次。

递归法代码

class Solution {
private:
    int maxCount = 0; // 最大频率
    int count = 0; // 统计频率
    TreeNode* pre = NULL;
    vector<int> result;
    void searchBST(TreeNode* cur) {
        if (cur == NULL) return ;

        searchBST(cur->left);       // 左
                                    // 中
        if (pre == NULL) { // 第一个节点
            count = 1;
        } else if (pre->val == cur->val) { // 与前一个节点数值相同
            count++;
        } else { // 与前一个节点数值不同
            count = 1;
        }
        pre = cur; // 更新上一个节点

        if (count == maxCount) { // 如果和最大值相同,放进result中
            result.push_back(cur->val);
        }

        if (count > maxCount) { // 如果计数大于最大值频率
            maxCount = count;   // 更新最大频率
            result.clear();     // 很关键的一步,不要忘记清空result,之前result里的元素都失效了
            result.push_back(cur->val);
        }

        searchBST(cur->right);      // 右
        return ;
    }

public:
    vector<int> findMode(TreeNode* root) {
        count = 0;
        maxCount = 0;
        pre = NULL; // 记录前一个节点
        result.clear();

        searchBST(root);
        return result;
    }
};

迭代法

同样的思路,只要是中序遍历就行。

迭代法代码

class Solution {
public:
    vector<int> findMode(TreeNode* root) {
        stack<TreeNode*> st;
        TreeNode* cur = root;
        TreeNode* pre = NULL;
        int maxCount = 0; // 最大频率
        int count = 0; // 统计频率
        vector<int> result;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指针来访问节点,访问到最底层
                st.push(cur); // 将访问的节点放进栈
                cur = cur->left;                // 左
            } else {
                cur = st.top();
                st.pop();                       // 中
                if (pre == NULL) { // 第一个节点
                    count = 1;
                } else if (pre->val == cur->val) { // 与前一个节点数值相同
                    count++;
                } else { // 与前一个节点数值不同
                    count = 1;
                }
                if (count == maxCount) { // 如果和最大值相同,放进result中
                    result.push_back(cur->val);
                }

                if (count > maxCount) { // 如果计数大于最大值频率
                    maxCount = count;   // 更新最大频率
                    result.clear();     // 很关键的一步,不要忘记清空result,之前result里的元素都失效了
                    result.push_back(cur->val);
                }
                pre = cur;
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点5 和节点 1 的最近公共祖先是节点 3 。

递归法

我们可以这样形象地理解最近公共祖先,从p、q两个节点向根节点前进,形成的路径相交的节点就是最近公共祖先。

从这个理解出发我们可以想到回溯,使用后序遍历。

让每个函数向上返回以当前节点为根节点的子树是否包含p、q,两个都包含就返回当前节点,包含其中一个就返回那一个节点。如果没有就返回null。

对于递归中的一个节点,只要左右孩子不同时为null,说明这个节点就是最近公共祖先即路径相交的节点(注意题目规定二叉树内没有重复的元素,所以才成立)。

以上的程序逻辑中,目标节点、p、q都会被一层层向上传递,节点不会关心收到的元素是否是目标节点。

递归法代码

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == q || root == p || root == NULL) return root;
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
        if (left != NULL && right != NULL) return root;
        if (left == NULL) return right;
        return left;
    }
};