Leetcode 530.二叉搜索树的最小绝对差
题目链接: 530.二叉搜索树的最小绝对差
题干: 给你一个二叉搜索树的根节点 root ,返回树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
思考一: 中序遍历递归法。设置两个成员变量pre记录上一个节点,result记录最小差值。在pre不为空的情况处理当前递归节点,比较当前节点 val值和前一个节点pre的val值的差值与result,更新result和pre。
代码:
class Solution {
public:
TreeNode* pre = nullptr; //记录上一个节点
int result = __INT_MAX__; //记录最小差值
void traversal(TreeNode* node) {
if (!node) return;
traversal(node->left);
if (pre)
result = min(result, node->val - pre->val);
pre = node; //更新节点
traversal(node->right);
}
int getMinimumDifference(TreeNode* root) {
traversal(root);
return result;
}
};
思考二: 中序遍历迭代法。在中序遍历迭代写法的基础上简单修改循环中出栈元素后的处理逻辑:改为更新最小差值和记录节点
中序遍历迭代写法:第十四天| 二叉树的递归遍历、二叉树的迭代遍历、二叉树的统一迭代法
代码:
class Solution {
public:
int getMinimumDifference(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* pre = nullptr;
int result = __INT_MAX__;
while (!st.empty() || cur) {
if (cur) {
st.push(cur);
cur = cur->left; //左 指针访问到最左下
} else {
cur = st.top(); st.pop(); //栈顶存放着左孩子为空或者左孩子已经访问过的节点
if (pre)
result = min(result, cur->val - pre->val); //中
pre = cur;
cur = cur->right; //右
}
}
return result;
}
};
思考三: 中序遍历统一迭代法。在中序遍历统一迭代写法的基础上简单修改循环中的处理逻辑:改为更新最小差值和记录节点
中序遍历统一迭代写法:第十四天| 二叉树的递归遍历、二叉树的迭代遍历、二叉树的统一迭代法
代码:
class Solution {
public:
int getMinimumDifference(TreeNode* root) {
stack<TreeNode*> st;
int result = __INT_MAX__;
if (!root) return result;
TreeNode* cur = root;
TreeNode* pre = nullptr;
st.push(root);
while (!st.empty()) {
TreeNode* cur = st.top();
if (cur != nullptr) {
st.pop();
if (cur->right != nullptr) st.push(cur->right); //右
st.push(cur); //中
st.push(nullptr);//null标志 说明后面的节点访问过
if (cur->left != nullptr) st.push(cur->left); //左
} else { //遇到null统一输出栈中后一个元素
st.pop();
cur = st.top();
st.pop();
if (pre)
result = min(result, cur->val - pre->val); //中
pre = cur;
}
}
return result;
}
};
Leetcode 501.二叉搜索树中的众数
题目链接:501 二叉搜索树中的众数
题干:给你一个含重复值的二叉搜索树(BST)的根节点
root
,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。如果树中有不止一个众数,可以按 任意顺序 返回。假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
思考一:按普通二叉树来解题。先通过二叉树遍历(前中后,层序均可)统计各元素出现频率。接着给容器内元素进行排序,最后将同排序后的首个元素出现频率相同的元素添加到最终结果内。
代码:
class Solution {
public:
//前序遍历
void traversal(TreeNode* node, unordered_map<int,int>& map) {
if (!node) return;
map[node->val]++; //统计频率
traversal(node->left, map);
traversal(node->right, map);
}
//比较规则
bool static cmp (const pair<int, int>& a, const pair<int, int>& b) {
return a.second > b.second;
}
vector<int> findMode(TreeNode* root) {
unordered_map<int,int> map; //key:元素值 value:出现频率
vector<int> result;
if (!root) return result;
traversal(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++) {
if (vec[i].second == vec[0].second)
result.push_back(vec[i].first); //同频率元素值加入容器
else break;
}
return result;
}
};
思考二:按二叉搜索树考虑递归法解题。
中间节点处理逻辑的关键点:
- 遍历有序数组的元素出现频率,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以。对于二叉树的比较,用指针pre记录前一个节点,这样当前节点cur可以和前一个节点pre作比较。
- 要求最大频率的元素集合。先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合。(因为众数有多个)但这样遍历两遍数组,时间复杂度高。
- 为减少时间复杂度,如果频率count 等于 maxCount(最大频率),就把这个元素加入到结果集中(以下代码为result数组)。当然为防止当前记录的maxCount不是真正的最大频率,每次递归处理中还要进行判断,如果频率count大于记录的maxCount,则清空前面记录的结果集,更新记录的maxCount以及将元素添加到结果集。
代码:
class Solution {
public:
TreeNode* pre = nullptr;
vector<int> result;
int maxCount = 0; //统计最大频率
int count = 0; //元素出现频率
void traversal(TreeNode* node) {
if (!node) return;
traversal(node->left);
if (!pre) count = 1; //首个元素设置频率初始值
else if (pre->val == node->val) count++; //前一个节点和当前节点的相同则频率增一
else count = 1; //前一个节点和当前节点的不相同则重新设置频率初始值
pre = node;
if (count == maxCount) result.push_back(node->val); //频率相同则加入容器
if (count > maxCount) { //当前元素出现频率高于记录频率
maxCount = count;
result.clear(); //清空前面记录的元素
result.push_back(node->val);
}
traversal(node->right);
}
vector<int> findMode(TreeNode* root) {
result.clear();
traversal(root);
return result;
}
};
思考三:按二叉搜索树考虑迭代法解题。中间节点处理逻辑关键点几乎无异。( 统一迭代法也一样,下面只给出迭代写法)
中序遍历迭代写法:第十四天| 二叉树的递归遍历、二叉树的迭代遍历、二叉树的统一迭代法
代码:
class Solution {
public:
vector<int> findMode(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* pre = nullptr;
int count = 0; //统计频率
int maxCount = 0; //最大频率
vector<int> result;
while(cur != nullptr || !st.empty()) {
if (cur != nullptr) {
st.push(cur);
cur = cur->left; //左 指针访问到最左下
} else {
cur = st.top(); //栈顶存放着左孩子为空或者左孩子已经访问过的节点
st.pop();
if (!pre) 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(); //清空前面记录的元素
result.push_back(cur->val);
}
cur = cur->right; //右
}
}
return result;
}
};
Leetcode 236. 二叉树的最近公共祖先
题目链接:236 二叉树的最近公共祖先
题干:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
- 所有节点的值都是唯一的。
- p、q 为不同节点且均存在于给定的二叉树中。
思考:递归法。考虑到查询公共祖先节点想到从底向上查询,二叉树的回溯过程就是从底往上的过程。后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
如何判断一个节点是节点q和节点p的公共祖先(两种情况):
- 情况一:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。(注意误区,题干写明节点值唯一,故不存在左右子树均返回同一目标节点)
- 情况二:节点本身p(q),它拥有一个子孙节点q(p),但与情况一同时完成。如果作为父节点的目标节点被查询到并返回,其他子二叉树未查找的均返回null,不影响目标节点的返回(具体实现看代码)
代码:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root) return nullptr;
if (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; //查找到公共祖先
else if (left && !right) return left; //左子树查找到目标节点
else if (!left && right) return right; //右子树查找到目标节点
else return nullptr;
}
};
自我总结:
- 再次理解递归三部曲中的确定递归函数的参数和返回类型。 什么时候要返回值,什么时候不要返回值?
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。
- 开始理解回溯法。回溯是递归的副产品,解决的都是在集合中递归查找子集
- 了解在元素比较中用指针代替基本数据类型记录,规避基本数据类型数据表示范围的诸多问题
- 了解容器清空功能的用途,开阔思路