代码随想录算法训练营第二十一天| 530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236. 二叉树的最近公共祖先

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

题目链接

思路1:要求二叉手术的最小绝对差,根据二叉搜索树的结构。可以直接中序遍历,得到一个升序数组,那么最小的值之差就用相邻的数组元素相减得到。

思路2:(双指针法)节约内存
遍历的时候用双指针,每次遍历的时候取相邻元素做差,和结果比较获得最小值。

class Solution {
public:
    int result = INT_MAX;
    TreeNode* pre = nullptr;

    void travesal(TreeNode* cur) {
        if (cur == nullptr) return;
        travesal(cur->left);
        if (pre != nullptr) {
            result = min(result, cur->val - pre->val);
        }
        pre = cur;

        travesal(cur->right);
    }

    int getMinimumDifference(TreeNode* root) {
        travesal(root);
        return result;

    }
};

总结:和验证二叉树是同一种做法,思路是一样的,只是在遍历过程中的处理改变即可,这是二叉搜索树需要掌握的关键。

501.二叉搜索树中的众数

题目链接

暴力解法(优化): 首先进行中序遍历的时候将出现的值统计进哈希表,然后遍历哈希表,同时记录一个当前值来帮助遍历,当出现次数大于count,那么进行更新,这里更新按照代码随想录上来解决。

class Solution {
public:
    vector<int> result;
    unordered_map<int, int> mp;
    void traversal(TreeNode* root) {
        if (root == nullptr) return;
        traversal(root->left);
        mp[root->val]++; // 统计出现的次数,中序遍历已经排好序
        traversal(root->right);
    }

    vector<int> findMode(TreeNode* root) {

        traversal(root);
        int count = 1;
        // 哈希处理部分, 值,出现次数
        for (auto val : mp) {
            if (val.second == count) result.push_back(val.first);
            else if (val.second > count) {
                count = val.second;
                result.clear();
                result.push_back(val.first);
            }
        }
        return result;
    }
};

思路2:双指针
遍历有序数组的元素出现频率,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。

在二叉树:搜索树的最小绝对差中我们就使用了pre指针和cur指针的技巧,这次又用上了。

弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。
而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。

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;
        TreeNode* pre = NULL; // 记录前一个节点
        result.clear();

        searchBST(root);
        return result;
    }
};

总结:对于双指针法的思路没有完全搞定,但是暴力解法的思路还是比较好理解的。双指针法主要就是在遍历的时候处理中比较麻烦,但是应该要注重每一步的逻辑,只要明白双指针比较的过程中的几个数怎么比较的,那么这道题相对来说就会简单。

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

题目链接

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

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

思路:遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。

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

首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。 即情况一:判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。
但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q§。 情况二:
其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。

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

递归法:

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 && right != NULL) return right;
        else if (left != NULL && right == NULL) return left;
        else  { //  (left == NULL && right == NULL)
            return NULL;
        }

    }
};

总结
1、求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。

2、在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。

3、要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。

你可能感兴趣的:(算法,leetcode,数据结构)