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

第六章 二叉树part07

今日内容

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

● 501.二叉搜索树中的众数

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


详细布置

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

需要领悟一下二叉树遍历上双指针操作,优先掌握递归

题目链接/文章讲解:代码随想录

视频讲解:https://www.bilibili.com/video/BV1DD4y11779

【链接】(文章,视频,题目)

【第一想法与实现(困难)】

  • 第一思路递归,左子树最小绝对差,右子树最小绝对差,根-左最大,根-右
class Solution {
public:
    // 第一思路递归,左子树最小绝对差,右子树最小绝对差,根-左最大,根-右最小
    // 二叉搜索树性质:最大在递归右孩子,最小在递归左孩子
    // 递归三部曲,返回值与参数,结束条件,递归主逻辑
    int getMinimumDifference(TreeNode* root, int* min_ptr, int* max_ptr) {
        if (!root) {
            return INT_MAX;
        }
        // 返回需要求的最大或者最小值
        if (min_ptr) {
            TreeNode* cur = root;
            while (cur->left) {
                cur = cur->left;
            }
            *min_ptr = cur->val;
        }
        if (max_ptr) {
            TreeNode* cur = root;
            while (cur->right) {
                cur = cur->right;
            }
            *max_ptr = cur->val;
        }
        // 递归主逻辑
        int res = INT_MAX;
        if (root->left) {
            int left_max(0);
            int left_res = getMinimumDifference(root->left, nullptr, &left_max);
            res = std::min(res, left_res);
            res = std::min(res, std::abs(root->val - left_max));
        }
        if (root->right) {
            int right_min(0);
            int right_res = getMinimumDifference(root->right, &right_min, nullptr);
            res = std::min(res, right_res);
            res = std::min(res, std::abs(right_min - root->val));
        }
        return res;
    }
    int getMinimumDifference(TreeNode* root) {
        return getMinimumDifference(root, nullptr, nullptr);
    }
};

【看后想法】

  • 二叉搜索树,中序遍历就是有序的!所有二叉搜索树的求差值最值,转换成有序数组的差值最值,就很简单!

  • 本题中序遍历递归,转换成有序数组,遍历数组求最小绝对差,就很简单。

class Solution {
public:
    std::vector<int> vec;
    void MidOrderTraverse(TreeNode* root) {
        if (!root) {
            return;
        }
        MidOrderTraverse(root->left);
        vec.emplace_back(root->val);
        MidOrderTraverse(root->right);
    }
    int getMinimumDifference(TreeNode* root) {
        // 中序遍历转换成有序数组,再遍历
        vec.clear();
        MidOrderTraverse(root);
        // 有序数组求最小绝对差
        int res(INT_MAX);
        if (vec.size() < 2) {
            return 0;
        }
        for (int i = 1; i < vec.size(); ++i) {
            res = std::min(res, vec[i] - vec[i - 1]);
        }
        return res;
    }
};
  • 也可以不转换成数组,直接在中序遍历过程记录去前序结点pre。因为真正处理cur的时候,就表示到了有序数组已经到了这个位置,而前序就是刚才处理过的结点。所以只要把处理完的cur存到pre就可以了。
class Solution {
public:
    // 不转换成数组,直接记录前序结点
    int res = INT_MAX;
    TreeNode* pre = nullptr;
    void MidOrderTraverse(TreeNode* cur) {
        if (!cur) {
            return;
        }
        MidOrderTraverse(cur->left);
        if (pre) {
            res = std::min(res, cur->val - pre->val);
        }
        pre = cur;
        MidOrderTraverse(cur->right);
    }
    int getMinimumDifference(TreeNode* root) {
        MidOrderTraverse(root);
        return res;
    }
};
  • 迭代方法中序遍历,统一方法,标
class Solution {
public:
    // 迭代方法中序遍历,统一方法,标记法
    int getMinimumDifference(TreeNode* root) {
        int res = INT_MAX;
        TreeNode* pre = nullptr;
        std::stack<TreeNode*> st; // 空节点表示需要处理。
        if (root) {
            st.push(root);
        }
        while (!st.empty()) {
            TreeNode* cur = st.top();
            if (cur) {
                // 访问即可,中序遍历左中右,入栈反过来
                st.pop(); // 本节点访问
                if (cur->right) {
                    // 树中空节点不入栈
                    st.push(cur->right);
                }
                st.push(cur);
                st.push(nullptr); // 本节点访问完成,需要处理
                if (cur->left) {
                    st.push(cur->left);
                }
            } else {
                // 需要处理
                st.pop();
                cur = st.top();
                st.pop();
                if (pre) {
                    res = std::min(res, cur->val - pre->val);
                }
                pre = cur;
            }
        }
        return res;
    }
};

【实现困难】太久没做题,对二叉树遍历都生疏了。二叉搜索树的性质要记得,中序遍历有序~!

【自写代码】

【收获与时长】1h15m


501.二叉搜索树中的众数

【链接】(文章,视频,题目)

和 530差不多双指针思路,不过 这里涉及到一个很巧妙的代码技巧。

可以先自己做做看,然后看我的视频讲解。

代码随想录

视频讲解:https://www.bilibili.com/video/BV1fD4y117gp

【第一想法与实现(困难)】

直接转换成数组,但是比较麻烦没写出来,要处理值变化与到最后一个

【看后想法】

  • 不是二叉搜索树的一般解法,map统计元素(key)

    • 注意sort的cmp函数要写成静态static,从而可以实现不基于具体对象的调用。又或者写成全局函数。
class Solution {
private:
    void SearchBst(TreeNode* root, std::unordered_map<int, int>* umap) {
        if (!root) {
            return;
        }
        (*umap)[root->val]++;
        SearchBst(root->left, umap);
        SearchBst(root->right, umap);
    }
    bool static cmp(const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) {
        return lhs.second > rhs.second;
    }
public:
    // 不是二叉搜索树的一般解法,map统计元素(key)-频率(val),vec排序
    vector<int> findMode(TreeNode* root) {
        std::unordered_map<int, int> umap; // 元素-出现频率
        SearchBst(root, &umap);
        std::vector<int> res;
        if (umap.empty()) {
            return res;
        }
        std::vector<std::pair<int, int>> vec(umap.begin(), umap.end());
        std::sort(vec.begin(), vec.end(), cmp); // 按照频率从大到小排序
        res.emplace_back(vec.front().first);
        for (int i = 1; i < vec.size(); ++i) {
            if (vec[i].second == vec.front().second) {
                res.emplace_back(vec[i].first);
            }
        }
        return res;
    }
};
  • 已知是二叉搜索树,可以使用中序遍历,记录前值pre。两轮遍历先求最大频率再求众数。一轮遍历,实时更新最大频率,如果有更大则前面全部失效。
class Solution {
    // 已知是二叉搜索树,可以使用中序遍历,记录前值pre。一轮遍历,实时更新最大频率,如果有更大则前面全部失效。
private:
    TreeNode* pre = nullptr;
    int cur_count = 0;
    int max_count = 0;
    std::vector<int> res;
public:
    void MidOrderTraverse(TreeNode* root) {
        if (!root) {
            return;
        }
        MidOrderTraverse(root->left);
        // 处理
        if (!pre) {
            cur_count = 1;
        } else if (pre->val == root->val) {
            cur_count++;
        } else {
            cur_count = 1;
        }
        pre = root;
        // 判断新众数
        if (cur_count == max_count) {
            res.emplace_back(root->val);
        } else if (cur_count > max_count) {
            max_count = cur_count;
            res.clear();
            res.emplace_back(root->val);
        }
        MidOrderTraverse(root->right);
    }
    vector<int> findMode(TreeNode* root) {
        pre = nullptr;
        cur_count = 0;
        max_count = 0;
        res.clear();
        MidOrderTraverse(root);
        return res;
    }
};
  • 中序遍历,迭代。重新理解了一下非统一的中序迭代方法,优点是简洁,但是需要更多的理解
class Solution {
public:
    // 中序遍历,迭代方法
    vector<int> findMode(TreeNode* root) {
        std::stack<TreeNode*> st;
        TreeNode* cur = root;
        TreeNode* pre = nullptr;
        int cur_count(0), max_count(0);
        std::vector<int> res;
        // 指针表示正访问结点,栈存储已经访问、等待处理的结点
        while (cur || !st.empty()) {
            if (cur) { // 直到底层的左访问
                st.push(cur);
                cur = cur->left;
            } else {
                // 中处理,右访问
                // 中处理——记录cur_count
                cur = st.top();
                st.pop();
                if (!pre) {
                    cur_count = 1;
                } else if (pre->val == cur->val) {
                    cur_count++;
                } else {
                    cur_count = 1;
                }
                // 中处理——比较max_count,更新众数
                if (cur_count == max_count) {
                    res.emplace_back(cur->val);
                } else if (cur_count > max_count) {
                    max_count = cur_count;
                    res.clear();
                    res.emplace_back(cur->val);
                }
                pre = cur; // 保存前值
                // 右访问
                cur = cur->right;
            }
        }
        return res;
    }
};

【实现困难】重新看了一下中序迭代非统一方法,有点意思

【收获与时长】2h20m


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

本题其实是比较难的,可以先看我的视频讲解

代码随想录

视频讲解:https://www.bilibili.com/video/BV1jd4y1B7E2

【链接】(文章,视频,题目)

【第一想法与实现(困难)】

  • 层序遍历,保存每个节点的双亲节点与系列祖先,直到遍历到两个指定节点,找公共祖先,再比较最深的公共祖先(层数最大)。实现起来就很复杂,因为改变了树结点本身的数据结构,要增加双亲节点与系列祖先。

【看后想法】

  • 因为要找祖先,所以其实从底往上遍历是更好的,可以通过递归回溯来实现,后序遍历的左右中,正好可以实现回溯。

  • 注意题目有一个条件,p, q的值总是存在,树中各个节点值不重复。所以本题可以设计成:判断左子树有无出现(p或q),判断右子树有无出现(p或q),根据两个返回值处理中间结点。

  • 注意特殊情况,p就是q的祖先,此时递归结束条件应该是,root自身就是p或q其中一个个时候,直接返回,由于题目设置,(树的值不重复,pq均存在),此时先找到的就是所求的祖先。

  • 注意是搜索整棵树,而不是一条边。因为需要左右的所有返回值,才能对中间节点做处理。而不是找到pq其中一个就返回(递归函数设计成了是否包含p或q)

【实现困难】

【自写代码】

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

【收获与时长】40m。感觉题目的条件优点苛刻,要求树的值不重复,pq均存在。实际上节点值并没有用上,只用到了节点指针。节点值不重复可以去掉。但是整体看起来就像是一道设计 很巧妙的题目,专门设计了一道很巧妙的方法,实用性不大。刷过的就会做,没有就不会

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值