第六章 二叉树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均存在。实际上节点值并没有用上,只用到了节点指针。节点值不重复可以去掉。但是整体看起来就像是一道设计 很巧妙的题目,专门设计了一道很巧妙的方法,实用性不大。刷过的就会做,没有就不会