代码随想录C++算法训练,二叉树(day18)
二叉搜索树的最小绝对差
题目链接:530. 二叉搜索树的最小绝对差 - 力扣(LeetCode)
题目描述:
给你一个二叉搜索树的根节点 root
,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
示例 1:
输入:root = [4,2,6,1,3] 输出:1
示例 2:
输入:root = [1,0,48,null,null,12,49] 输出:1
题解:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
private:
int result = INT_MAX; // 初始化结果为整型最大值,用于后续比较
TreeNode* pre = nullptr; // 用于记录中序遍历过程中的前一个节点
// 递归遍历函数,计算BST中任意两节点的最小差值
void traversal(TreeNode* cur) {
// 递归终止条件:当前节点为空时直接返回
if (cur == nullptr) return;
// 1. 递归遍历左子树(中序遍历的第一步:左)
traversal(cur->left);
// 2. 处理当前节点(中序遍历的第二步:根)
if (pre != nullptr) { // 如果前一个节点不为空(说明不是第一个节点)
// 计算当前节点与前一个节点的差值,并更新全局最小差值
result = min(result, cur->val - pre->val);
}
pre = cur; // 更新pre为当前节点,供下一次比较使用
// 3. 递归遍历右子树(中序遍历的第三步:右)
traversal(cur->right);
}
public:
// 对外接口:返回BST中任意两节点的最小差值
int getMinimumDifference(TreeNode* root) {
traversal(root); // 调用递归函数遍历整棵树
return result; // 返回计算得到的最小差值
}
};
总结与思考:
- 此题与验证二叉搜索树逻辑相似,对应力扣98题,我们可以将给出的二叉搜索树转化为有序数组后,再进行最小绝对差的计算,或者可以直接在二叉搜索树中进行操作,我们这里直接选择在二叉搜索树中进行操作。
- 递归要注意的三个重点:
- 递归函数的参数和返回值:cur作为当前正在处理的树节点,因为该函数不需要直接返回计算结果,而是通过修改类的成员变量result和pre来记录状态,因此不需要返回值,类型为void
- 递归函数的结束条件:当递归到空节点时,说明遍历结束,退出递归
- 递归函数单层递归的逻辑:递归遍历左子树,随后进行根进行处理,再递归遍历右子树,重点为对根的处理逻辑,计算当前节点与前一个节点的差值,并更新全局的最小差值
二叉搜索树中的众数
题目链接:501. 二叉搜索树中的众数 - 力扣(LeetCode)
题目描述:
给你一个含重复值的二叉搜索树(BST)的根节点 root
,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
示例 1:
输入:root = [1,null,2,2] 输出:[2]
示例 2:
输入:root = [0] 输出:[0]
题解:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
private:
int maxCount = 0;//最大频率
int count = 0;//统计频率
TreeNode* pre = nullptr;//指向前一个元素
vector<int> result;//统计结果的数组
void searchBST(TreeNode* cur){
if(cur == nullptr) return;
//按照左中右的顺序进行递归和处理
searchBST(cur->left);
//重点处理“中”的逻辑
if(pre == nullptr){//第一个节点
count = 1;
}else if(pre->val == cur->val){//当前节点与前一个节点值相同,则频率加一
count++;
}else{//不同时,频率不加
count = 1;
}
//更新上一个节点
pre = cur;
//统计众数结果
if(count == maxCount){//如果统计频率与最大频率相同,则将值放入到结果数组中
result.push_back(cur->val);
}
if(count > maxCount){//如果计数大于最大频率
maxCount = count;//更新最大频率
result.clear();//若count>maxCount,则之前统计的result失效,需要清空
result.push_back(cur->val);//放入到结果数组中
}
searchBST(cur->right);
return;
}
public:
vector<int> findMode(TreeNode* root) {
int count = 0;
int maxCount = 0;
TreeNode* pre = nullptr;//记录前一个节点
result.clear();
searchBST(root);
return result;
}
};
总结与思考:
- 因为是寻找二叉搜索树中的众数,则其中序遍历一定是有序的,根据这一点我们可以使用中序遍历递归解法
- 该递归函数通过修改类的成员变量result和pre来记录状态,我们可以使用pre和cur指针的技巧,弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。
- 随后就是常规的中序递归的写法,但这里有一个重难点,我们如何将统计出来的众数作为一个结果数组result返回出来,我们可以遍历一次数组,找出其中最大频率对应的结果,将这个结果再遍历放入集合中,但这样效率不高,所以我们需要在处理“中”节点时做一些额外的处理
- 若找出的统计频率等于最大统计频率,则将这个频率对应下的元素直接放入结果集合中,若找出的统计频率大于最大统计频率,则更新最大统计频率后,将result数组清空,随后再重新将该频率对应下的元素放入结果集合中。这样我们就可以做到只需要遍历一次二叉搜索树,就找出了众数的集合
二叉树的最近公共祖先
题目链接:236. 二叉树的最近公共祖先 - 力扣(LeetCode)
题目描述:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出:3 解释:节点5
和节点1
的最近公共祖先是节点3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 输出:5 解释:节点5
和节点4
的最近公共祖先是节点5 。
因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2 输出:1
题解:
/**
* 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* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//递归结束条件,找到q或p或为空时,返回对应结果
if(root == q || root == p || root == NULL) return root;
//采用后序遍历,左右中的顺序处理
//递归左
TreeNode* left = lowestCommonAncestor(root->left, q, p);
//递归右
TreeNode* right = lowestCommonAncestor(root->right, q, p);
//递归中
//左右都找到的情况
if(left != NULL && right != NULL) return root;
//其他情况
if(left != NULL && right == NULL) return left;
else if(left == NULL && right != NULL) return right;
else{//左右都没找到
return NULL;
}
}
};
总结与思考:
- 寻找目标的最近公共祖先,我们需要自底向上遍历,因此这里利用回溯的特性:自底向上,而后序遍历的顺序,左右中就是天然的自底向上的过程,因此使用后序遍历递归来解题。
- 递归需要注意的三个要点:
- 递归函数的参数和返回值:我们需要寻找q,p节点的公共祖先root,因此参数为这三个树节点,返回值类型代表着是否找到了公共祖先,因此使用bool型
- 递归函数的结束条件:根节点为空的情况下说明是空树,直接返回,若我们找到了p,q时,直接返回节点值。
- 递归函数的单层递归逻辑:因为回溯的过程需要递归函数的返回值做判断,所以我们需要遍历整棵树的所有节点,具体的遍历逻辑体现在代码中