【代码随想录算法训练营第37期 day21 | LeetCode530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先】

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

由于二叉搜索树的特性,中序遍历时,输出的序列是一个单调递增的序列。

转成数组来看,在一个有序数组中,任意两个非连续元素的差值可以分解为连续元素差值的和。

例如,对于有序数组中的三个元素 a[i],a[i+1], a[i+2],

差值 ∣a[i]−a[i+2]∣ 可以分解为 ∣a[i]−a[i+1]∣+∣a[i+1]−a[i+2]∣。

也就是说最小的差值只会在两两做差中产生,其他的两个元素的差值能转换成连续的差值的和,这显然不是最小的,所有我们只需要计算 i-1 和 i 的两个节点的差值,并记录这些差值的最小值即可。

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:
    int getMinimumDifference(TreeNode* root) {
        vec.clear();
        traversal(root);
        if (vec.size() < 2) return 0;
        int result = INT_MAX;
        for (int i = 1; i < vec.size(); i++) { // 统计有序数组的最小差值
            result = min(result, vec[i] - vec[i-1]);
        }
        return result;
    }
};

上述的解法是转换成数组,有额外的空间开销,我们可以直接在二叉树上,按照中序遍历的顺序进行两两节点做差值。这里的难理解的点在于怎么记录上一个节点,这个上一个节点指的是按中序遍历的输出顺序的上一个。

以下是实现代码,在递归中如何记录前一个节点的指针,其实实现起来是很简单, pre = cur 这个操作就能实现,变量pre被用来保持对前一个访问过的节点的引用。初始化时,preNULL,表示还没有前一个节点。

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;
    }
};

501.二叉搜索树中的众数

如果不是二叉搜索树,最直观的方法一定是把这个树都遍历了,用map统计频率,把频率排个序,最后取前面高频的元素的集合。

但二叉树搜索树的中序遍历是有序的,这里还是利用这个特性,如果转成数组,这题的思路可以转换成,在这样一个非严格单增的有序序列中,找出现次数最多的数。

在这个序列中,相同的值都是挨在一块的,如11222334555。我们可以用两个指针,从头开始,两个两个比较,相同时count++,用maxcount维护一个最大的count。当count计数到和maxcount相等的时候,说明这个数的个数已经是已知的最大了,先将该数认为是众数,输出到结果集中,如果后续有更大的,再清空结果集,重新记录众数。

通过记录上一个节点,直接在树上进行两两比较的技巧和上一题一样。

我最开始的想法是在 cur 和 pre 两个节点的值不一致时再进行 maxcount 和 result 的判断和更新,写成如下形式:

if(pre == nullptr){
    count = 1;
}
if(pre != nullptr){
     if(cur->val == pre->val)
         count++;
     else{
         if(maxcount == count){
             result.push_back(pre->val);
         }
         if(maxcount < count){
             maxcount = count;
             result.clear();
             result.push_back(pre->val);
         }
         count = 1;
     }
}

但这会导致在结束递归之后无法处理最后一个元素的情况。具体来说,如果最后几个元素是众数,并且在完全遍历树之后发现它们的计数与 maxcount 相等,这种情况下代码没有进入 maxcount 的判断,再做一次检查来添加这些元素到结果集 result 中。因为在最后一次递归调用返回后,没有再对 precount 进行最终的比较和添加操作。

所以把这个判断以及更新放到外边,每次都进行判断和更新,这样更新的节点就得改成 cur 而不是 pre 。整体如下所示。

class Solution {
public:
    TreeNode* pre = nullptr;
    int count = 1;
    int maxcount = -1;
    void traversal(TreeNode* cur, vector<int>& result){
        if(cur == nullptr) return;
        traversal(cur->left, result);
        
        if(pre != nullptr && cur->val == pre->val) {
            count++;
        } else {
            count = 1;  // 遇到新值,计数重置
        }
        if(count == maxcount) {
            result.push_back(cur->val);
        } 
        else if(count > maxcount) {
            maxcount = count;
            result.clear();  // 发现更频繁的元素,清空结果列表并重新开始
            result.push_back(cur->val);
        }
        pre = cur;  // 更新前一个节点为当前节点

        traversal(cur->right, result);
    }

    vector<int> findMode(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};

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

记录思路,摘自代码随想录:代码随想录 (programmercarl.com)

遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。

那么二叉树如何可以自底向上查找呢?

回溯啊,二叉树回溯的过程就是从低到上。

后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。

接下来就看如何判断一个节点是节点q和节点p的公共祖先呢。

首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。

判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。

那么有录友可能疑惑,会不会左子树 遇到q 返回,右子树也遇到q返回,这样并没有找到 q 和p的最近祖先。

这么想的录友,要审题了,题目强调:二叉树节点数值是不重复的,而且一定存在 q 和 p

但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)。

其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。

因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。

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

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
       return traversal(root,p,q);
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值