文章目录
530.二叉搜索树的最小绝对差
文章链接:530.二叉搜索树的最小绝对差
视频链接:二叉搜索树中,需要掌握如何双指针遍历!| LeetCode:530.二叉搜索树的最小绝对差
状态:首先沿袭上一个验证二叉搜索树的思路,我们可以先转换为有序向量,然后计算,这样的话很简单。但是我们必须挑战一下,边遍历边计算。
好方法的话有三个问题
- 本题最好的方法肯定使用双指针来遍历,终止条件是什么
- 我们如何设置指针。
思路
直白思路
现在我们再问该问题:
判断二叉搜索树的最小绝对差是多少?
最直观的想法就是:用中序遍历搞成一个有序数组,然后求数组的最小绝对差
利用二叉搜索树特性
利用二叉搜索树的特性,最小绝对差必然出现在中序遍历后的相邻结点之间。因为中序遍历是按照递增顺序来阻止的,最小绝对差肯定是相邻的。
我们用双指针,在中序遍历的时候直接比较绝对差,这样就不用额外构造数组了。
比如说,我们用一个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::map
、std::multimap
的value
进行排序,只能对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
, 这样当pre
为NULL
的时候,我们就知道这是比较的第一个元素。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结点的深度不一样的话那应该咋整。怎么让他们速度一致。
回答:节点深度不一样,如何保持一致?
如果我们采用后序遍历作为递归函数,左节点递归完成之后,右节点递归不结束,中间节点是不会 return 的,能提出这个问题属于是寄寄的了,没有深刻理解二叉树的遍历过程。
直接烂透,没搞清楚二叉树的遍历过程,无论是什么遍历方法,处理顺序都只能是从下往上去处理。所以我们这里一定要用回溯,实现自底向上的处理。
思路
最直观的思路就是我们先找到待查结点p, q。
然后我们往上去遍历,只要汇聚到一个结点上,那么这个结点就是我们的最近公共祖先 。
但是对于二叉树来说,我们只能从根结点开始往下去遍历。但是我们的处理顺序却可以从下往上去处理。
用什么样的方法去实现呢?
答案就是回溯!回溯的过程就可以实现从底往上去处理我们的结果
后序遍历是天然的回溯过程,可以根据左右子树的返回值,来处理中结点的逻辑。那么如何判断一个结点是结点q 和结点p 的公共祖先呢?
我们根据情况的从易到难进行推理。
情况一:如果找到一个结点,发现左子树出现结点p,右子树出现结点q,或者左子树出现结点q,右子树出现结点p,那么该结点就是结点p和q的最近公共祖先。
判断逻辑就是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先
情况二:节点本身p(q),它拥有一个子孙节点q(p):
其实情况一和情况二代码实现逻辑是一样一样的。情况二同一
基本流程如下:
- 遍历整颗树
- left为空,right不为空,返回right
- left不为空,right为空,返回left
流程同上图 - 完整流程
伪代码
- 递归函数的返回值和参数:返回二叉树的公共祖先;然后传进来的参数就是我们的根结点,还有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;
}
};