Day21【二叉树】530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236.二叉树的最近公共祖先

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

题目链接/文章讲解/视频讲解

本题又是二叉搜索树!看到二叉搜索树就想到一点:中序遍历得到的序列结果是单调递增的

本题要求树中任意两不同节点值之间的最小差值,而单调递增序列中,若任意两元素的绝对差最小,则这两个元素一定相邻。故本题就是求二叉搜索树中序遍历结果中相邻两元素绝对差的最小值

直观思想:中序遍历该树得到一个数组,然后在这个数组上操作

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

想想Day20的二叉搜索树题目:我们能不能在遍历二叉树的过程中实现我们期望的工作?

本题要求相邻节点值的绝对差最小值,需要时时刻刻记录前一个节点,然后遍历到某个节点时,记录与前一个节点的值的绝对差

思路和Day20那道题一样,都是遍历二叉树过程中的双指针

class Solution {
private:
    TreeNode * pre = nullptr;
    int res = INT_MAX;
public:
    int getMinimumDifference(TreeNode* root) {
        traversal(root);
        return res;
    }
    void traversal(TreeNode * root) {
        if (root == nullptr) return;
        traversal(root -> left);    // 左
        if (pre != nullptr)
            res = min(res, root -> val - pre -> val);   // 中
        pre = root;
        traversal(root -> right);    // 右 
        return;
    }
};

因为是中序,递归函数 traversal 访问到的节点一定是按照遍历顺序递增的,需要一个 pre 始终指向前一个节点,traversal 处理的节点为当前节点,在中序处理当前节点。处理完当前节点后记得将 pre 置为当前节点,便于继续进入遍历右子树的递归逻辑 

501.二叉搜索树中的众数

 题目链接/文章讲解/视频讲解

直观思想:遍历该树,用map记录各个元素出现的次数,然后对map中的元素按照value排序。但是C++中的map只能定制关键字的比较操作,所以要把map转化为数组vector,再进行排序,vector里面放的也是 pari<int,int> 的数据,第一个int为元素,第二个int为出现频率

class Solution {
private:

void searchBST(TreeNode* cur, unordered_map<int, int>& map) { // 前序遍历
    if (cur == NULL) return ;
    map[cur->val]++; // 统计元素频率
    searchBST(cur->left, map);
    searchBST(cur->right, map);
    return ;
}
struct cmp{
    bool operator()(const pair<int, int> &lhs, const pair<int, int> &rhs) {
        return lhs.second > rhs.second;
    }
};
public:
    vector<int> findMode(TreeNode* root) {
        unordered_map<int, int> map; // key:元素,value:出现频率
        vector<int> result;
        if (root == NULL) return result;
        searchBST(root, map);
        vector<pair<int, int>> vec(map.begin(), map.end());
        sort(vec.begin(), vec.end(), cmp()); // 给频率排个序
        result.push_back(vec[0].first);
        for (int i = 1; i < vec.size(); i++) {
            // 取最高的放到result数组中
            if (vec[i].second == vec[0].second) result.push_back(vec[i].first);
            else break;
        }
        return result;
    }
};

注意,sort 的第三个参数是一个谓词(可调用表达式),这个表达式能够通过()运算符调用,若采用仿函数(可调用对象)的方式,cmp类中重载的运算符的访问权限应该是public

注意,上面那个方法适用于普通二叉树,没有用到二叉搜索树的特性 

然而,本题又是二叉搜索树!看到二叉搜索树就想到一点:中序遍历得到的序列结果是单调递增的

本题改了二叉搜索树的定义,因此中序遍历得到的序列结果单调不减 

我们可以仿照之前的做法,在中序遍历二叉树的过程中实现我们期望的节点处理工作

仍然是遍历树过程中的双指针运用,需要维护一个 pre,在处理完某节点的时候需要更新 pre

本题主要是代码小技巧 

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;   // 更新最大频率,能够保证,最后的maxCount一定是整个树中出现次数最多的元素的出现频率
            result.clear();     // 很关键的一步,不要忘记清空result,之前result里的元素都失效了,能够保证结果数组里面出现的元素一定是最高频元素
            result.push_back(cur->val);
        }

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

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

        searchBST(root);
        return result;
    }
};

此外说一点,我们今天的题目将递归三部曲的一步一步分析省略了。其实定义的递归函数功能是“中序遍历该树并依次处理该树的所有节点”,然后中序遍历某棵树并处理其所有节点需要依次遍历处理其左子树、根、右子树。其中,遍历并处理子树的操作能够递归调用函数本身

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

题目链接/文章讲解/视频讲解

注意题干:节点互不相同、指定节点均存在 

定义递归函数功能遍历一棵树,并判定指定节点是否在这棵树里 

然后递归三部曲

确定递归函数参数和返回值:需要输入一棵树,以及按照题意需要指定两个节点传入,返回 bool

bool traversal(TreeNode* root, TreeNode* p, TreeNode* q)

确定递归函数终止条件:注意关注我们递归函数的功能是判定指定节点是否在树里

当为 root 为空,空树,则指定节点必不存在于空树,直接返回 false

当 root 本身的值就是指定节点值的时候,直接返回 true

if (root == nullptr) return false;

if (root -> val == p -> val || root -> val == q -> val) return true;

确定单层递归逻辑:当 root 不为空,我们需要分别在左子树和右子树判定是否存在节点。在某个子树寻找节点能够通过递归函数本身实现

bool traversal(TreeNode * root, TreeNode * p, TreeNode * q)     // 判断树是否包含节点p或q
{
    if (root == nullptr) return false;
    if (root -> val == p -> val || root -> val == q -> val) return true;
    bool left = traversal(root -> left, p, q);
    bool right = traversal(root -> right, p, q);
    return left || right;
}

通过上述的递归遍历代码,我们能够遍历整棵树的所有节点,并对每个节点判定目标节点是否在以该节点为根的子树里面

然后回到我们的目标,我们要找的是最近公共祖先 

最近公共祖先是一个特殊的节点,该节点只有如下两种情况

第一种情况:该根节点的左子树中存在指定节点右子树中存在指定节点

第二种情况:该根节点本身是指定节点该根节点的左子树或者右子树中存在指定节点

有可能满足这两个条件的节点在树中唯一!即最近公共祖先 

在我们遍历的过程中,如果遇到了这样的节点,我们就将其存储下来作为结果 res

只需要在上述代码中,添加这种逻辑

if ((root -> val == p -> val || root -> val == q -> val) && (left || right)) 
    res = root;    // 第二种情况
else if (left && right) // 第一种情况
    res = root;

整体代码 

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode * res;
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        traversal(root, p, q);
        return res;
    }
    bool traversal(TreeNode * root, TreeNode * p, TreeNode * q) {
        if (root == nullptr) return false;

        bool left = traversal(root -> left, p, q);
        bool right = traversal(root -> right, p, q);

        // 添加判断为公共祖先的代码逻辑
        if ((root -> val == p -> val || root -> val == q -> val) && (left || right)) res = root;
        else if (left && right) res = root;

        if (root -> val == p -> val || root -> val == q -> val) return true;
        
        return left || right;
    }
};

注意,因为 root 本身的值就是指定节点值的时候,可能需要记录该 root,而不能直接返回,因此需要把 root 本身的值就是指定节点值时返回 true 的逻辑后移 


回顾总结 

掌握二叉树上用双指针法遍历的技巧

递归三部曲应该非常熟练了,比较难的就是如何定递归函数功能,以及如何利用递归函数,在236.二叉树的最近公共祖先中,定义了递归函数,然后为了利用这个递归函数,还对其进行了一定的修改

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林沐华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值