代码随想录算法训练营DAY21|C++二叉树Part.7|530.二叉搜索树的最小绝对差、501. 二叉搜索树中的众数、236.二叉树的最近公共祖先

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

力扣题目链接

文章链接:530.二叉搜索树的最小绝对差

视频链接:二叉搜索树中,需要掌握如何双指针遍历!| LeetCode:530.二叉搜索树的最小绝对差

状态:有三个问题

  1. 本题最好的方法肯定使用双指针来遍历,终止条件是什么
  2. 我们如何设置指针。

思路

直白思路

现在我们再问该问题:

判断二叉搜索树的最小绝对差是多少?

最直观的想法就是:用中序遍历搞成一个有序数组,然后求数组的最小绝对差

利用二叉搜索树特性

利用二叉搜索树的特性,最小绝对差必然出现在中序遍历后的相邻结点之间。因为中序遍历是按照递增顺序来阻止的,最小绝对差肯定是相邻的

我们用双指针,在中序遍历的时候直接比较绝对差,这样就不用额外构造数组了。

比如说,我们用一个cur和一个pre指针,pre指针则一直跟在cur的屁股后面跑。
这个是如何实现的呢?

首先在递归函数外面定义一个空指针 TreeNode* pre = NULL;

然后在递归函数内,调用下一次递归前使用 pre = cur;即可实现

同时遍历顺序务必要满足中序遍历(让遍历过程是有序的),这样的比较才是有意义的。

直白想法CPP代码

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;
        //这里的i为什么要用1?
        //首先题目要求最小绝对差是个正数,所以我们最好拿有序数组后面的减去前面的
        //即vec[i] - vec[i-1]为了防止超出索引范围,我们这里当然令i = 1
        for (int i = 1; i < vec.size(); i++) { // 统计有序数组的最小差值
            result = min(result, vec[i] - vec[i-1]);
        }
        return result;
    }
}

双指针法

  • 先定义全局变量
    • result 用来记录我们相邻结点差值的最小值
    • pre预先定义好,不然我们在迭代函数里面定义pre那不是乱套了?
int result = MAX_INT;
TreeNode* pre = NULL;
  • 确定参数和返回值:
    • 返回值是void,因为我们这里都有全局变量记录结果了,当然不需要返回值
    • 参数当前是我们的cur指针了(因为我们已经定义了全局变量pre)
void traversal(TreeNode* cur)
  • 确定终止条件。
if (cur == NULL) return;
  • 单层递归逻辑:这里的移动逻辑挺厉害的,一定要搞清楚。这里的pre完完全全有一种回溯的感觉
traversal(cur->lfet); //左
  
if (pre != NULL)
  result = min(result, cur->val - pre->val); //中
//----------最关键的过程:pre如何紧跟着cur指向cur的前一个结点--------------
pre = cur;
//------------------------------------------------------------------

traversal(cur->right); //右

501. 二叉搜索树中的众数

力扣题目链接

文章链接:501. 二叉搜索树中的众数

视频链接:不仅双指针,还有代码技巧可以惊艳到你! | LeetCode:501.二叉搜索树中的众数

状态:众数可以有多个!我们如何进行排查?首先肯定要掌握普通二叉树的众数寻找方法

如何利用二叉搜索树的特性来进行排查?双指针思路可以有效解决。既然利用双指针了,我们肯定不能有额外的存储空间,而是直接比较出它的出现频率。这应该如何实现呢?我们如何记录当前遍历元素的出现频率呢?

视为普通二叉树

如果是一颗普通二叉树的话,我们如何找众数呢?

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

这里一定要注意的就是C++不能对std::mapstd::multimapvalue进行排序,只能对key进行排序。后文会详细介绍解决方法

伪代码

  • 整个树都遍历一遍,用map统计频率。至于遍历顺序都无所谓
//map<int, int>key:元素,value:出现频率
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 ;
}
  • 根据出现频率排序(即map中的value)

C++中无法做到使用std::map或者std::multimap对value排序,只能对key排序。

所以只能把map先转换成数组,然后进行排序。vector里面放的是pair<int, int>,第一个int为元素,第二个int为出现的频率。

bool static cmp (const pair<int, int>& a, const pair<int, int>& b)
  return a.second > b.second;

vector<pair<int, int>> vec(map.begin(), map.end());
sort(vec.begin(), vec.end(), cmp); //给频率排个序
  • 取前面高频的元素

此时数组vector中已经存放着按照频率排好序的pair,那么把前面高频的元素取出来就可以。

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;

CPP代码

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 ;
	}
	bool static cmp (const pair<int, int>& a, const pair<int, int>& b) {
    	return a.second > b.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;
    }
};

利用二叉搜索树的特性

首先要确定的是,我们肯定用中序遍历,因为只有中序遍历二叉树才是有序的。只有这样,我们的遍历过程才有我们想象中的特性

那么问题来了,如何在这个有序的二叉树里求众数呢?

如果我们把这个二叉树想象成一个数组的话,当然也是要求相邻两个元素比较,然后把出现频率最高的元素输出就可以了。
数组上我们如何操作?前后两个指针相互比较

首先有一个maxCount,用来记录最高频率。然后有一个count,用来记录某个数出现的频率。我们使用双指针cur和pre,只有cur和pre相等我们count就+1,等到不相等的时候就重制count=1即可。至于 maxCount到后面的代码看它到底咋用的

并且准备一个result数组,来更新我们想要的结果集。

伪代码

  • 先定义全局变量:
    • pre指针:与上题中搜索二叉树的最小绝对差一致。定义一个指针指向前一个结点。而且初始化的时候pre = NULL, 这样当preNULL的时候,我们就知道这是比较的第一个元素。
    • count:用来记录当前元素的频率
    • maxCount:用来记录最大频率(在确定单层递归逻辑部分的代码可以看出如何使用)
    • result:用来存储结果
TreeNode* pre = NULL;
int count = 0;
int maxCount = 0;
vector<int> result;
  • 确定递归函数的参数和返回值。
void traversal(TreeNode* cur){
  
}
  • 确定终止条件
if (cur == NULL) return;
  • 确定单层递归逻辑
//左
traversal(cur->left);

//中
/************************统计单一元素出现频率的逻辑********************/
if (pre == NULL) count = 1; //表示pre还没指呢,目前改元素出现的频率为1
else if (pre->val == cur->val) count++;
else count = 1;
pre = cur; //让pre跟在cur的后面

if (count == maxCount) result.push_back(cur->val);
/*********************实现一次遍历同时记录maxCount和count还更新结果集*****/
//这段代码只能说牛逼
if (cout > maxCount){ 
  maxCount = count;//更新maxCount
  result.clear();//清空结果集,因为我们找到更高频率的值了,之前存放的结果集不行
  result.push_back(cur->val);
}

//右
traversal(cur->right);
return;

CPP代码

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

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

本题真的是领会二叉树遍历逻辑的最佳题型
力扣题目链接

文章链接:236. 二叉树的最近公共祖先

视频链接:自底向上查找,有点难度! | LeetCode:236. 二叉树的最近公共祖先

状态:第一眼就是要双指针,一个指向p,一个指向q,然后在后序遍历的过程中,两个指针最先相遇的那个结点就是了。但是这样带来了一个问题就是,如果p,q结点的深度不一样的话那应该咋整。怎么让他们速度一致。

直接烂透,没搞清楚二叉树的遍历过程,无论是什么遍历方法,处理顺序都只能是从下往上去处理。所以我们这里一定要用回溯,实现自底向上的处理

思路

最直观的思路就是我们先找到待查结点p, q。

然后我们往上去遍历,只要汇聚到一个结点上,那么这个结点就是我们的最近公共祖先 。

但是对于二叉树来说,我们只能从根结点开始往下去遍历。但是我们的处理顺序却可以从下往上去处理

用什么样的方法去实现呢?

答案就是回溯!回溯的过程就可以实现从底往上去处理我们的结果

后序遍历是天然的回溯过程,可以根据左右子树的返回值,来处理中结点的逻辑。那么如何判断一个结点是结点q 和结点p 的公共祖先呢?

我们根据情况的从易到难进行推理。
情况一:如果找到一个结点,发现左子树出现结点p,右子树出现结点q,或者左子树出现结点q,右子树出现结点p,那么该结点就是结点p和q的最近公共祖先。

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

情况二:节点本身p(q),它拥有一个子孙节点q§:
其实情况一和情况二代码实现逻辑是一样一样的。情况二同一


基本流程如下:

  1. 遍历整颗树
  2. left为空,right不为空,返回right
  3. left不为空,right为空,返回left
    流程同上图
  4. 完整流程

伪代码

  • 递归函数的返回值和参数:返回二叉树的公共祖先;然后传进来的参数就是我们的根结点,还有p, q两个值
TreeNode* traversal (root, q, p){
}
  • 确定终止条件(我认为这是本题最重要的逻辑):如果root为空的时候肯定就终止了(如果二叉树本来就是一颗空的二叉树,我们也直接return 空);如果我们遇到了p/q,把p/q往上返回,告诉上一层结点我们已经遇到了p/q,那么那个上层结点就是最小公共祖先喽,返回它即可
if (root == NULL) return NULL;
if (root == p || root == q) return root;

#精简
if(root == p || root == q || root == NULL) return root;
  • 单层递归逻辑:我们的递归函数是有返回值的,所以我们必须要认识清楚我们是搜索一条边,还是搜索整颗树。
    • 如果搜索一条边,写法应该是:if (递归函数(root->left)) return ;搜索完,立即返回
    • 如果搜索整颗树,我们需要用变量接住返回值,因为后序还有对该返回值进行处理,也就是后序遍历中处理中间结点的逻辑(也就是回溯)。本题中,我们是需要搜索整颗树来找最近公共祖先的。如下图所示:
TreeNode* left = traversal(root->left, q, p);//这里的left相当于告诉我们左子树是否有p或者q
TreeNode* right = traversal(root->right, q, p);

//中的处理逻辑
if (left != NULL && right != NULL) return root;	//左右都不为空

if (left == NULL && right != NULL) return right; //左空右不空
else if(left != NULL && right == NULL) return left; //右空左不空
else retuen NULL; //  (left == NULL && right == NULL)

CPP代码

#完整代码
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;
        }

    }
};

# 精简代码
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;
    }
};
  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值