leetcode 530.二叉搜索树的最小绝对差
leetcode 501.二叉搜索树中的众数
leetcode 236.二叉树的最近公共祖先
leetcode 530.二叉搜索树的最小绝对差
给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。
我们知道:二叉搜索树是有序的,所以我们可以遍历整个二叉搜索树,将其转化为一个数组,然后再遍历数组找到其最小绝对差。
代码如下:
/**
* 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:
vector tree;
void traversal(TreeNode* cur){
if(cur == NULL) return;
traversal(cur->left);
tree.push_back(cur->val);
traversal(cur->right);
}
public:
int getMinimumDifference(TreeNode* root) {
traversal(root);
if(tree.size() < 2) return 0;
int result = INT_MAX;
for(int i = 1; i < tree.size(); i++){
result = min(result, tree[i] - tree[i - 1]);
}
return result;
}
};
我们也可以在二叉树中序遍历的过程中就直接进行绝对差的计算与比较。
需要用一个pre节点记录一下cur节点的前一个节点。
如图:
递归三部曲:
确定递归函数的参数和返回值
需要定义一个int类型的result值并初始赋值为INT_MAX,作为每次与节点的绝对差相比较的值。递归函数传入根节点,我们需要遍历整个二叉树且不用处理递归返回值,所以是void类型。
int result = INT_MAX;
TreeNode* pre = NULL;
void traversal(TreeNode* cur)
确定终止条件
当前节点为空节点时返回
if(cur == NULL) return;
确定单层递归的逻辑
按照中序(左中右)遍历整个二叉树,中是对节点进行处理的操作,这里比较新的绝对差与result的大小并赋值pre,使pre节点记录cur的前一个节点。
traversal(cur->left);
if(pre != NULL){
result = min(result, cur->val - pre->val);
}
pre = cur;
traversal(cur->right);
整体代码如下:
class Solution {
private:
int result = INT_MAX;
TreeNode* pre = NULL;
void traversal(TreeNode* cur){
if(cur == NULL) return;
traversal(cur->left);
if(pre != NULL){
result = min(result, cur->val - pre->val);
}
pre = cur;
traversal(cur->right);
}
public:
int getMinimumDifference(TreeNode* root) {
traversal(root);
return result;
}
};
迭代法
根据之前写过的二叉树中序遍历迭代法,使用栈来实现迭代法:
/**
* 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 {
public:
int getMinimumDifference(TreeNode* root) {
stack st;
TreeNode* cur = root;
TreeNode* pre = NULL;
int result = INT_MAX;
while(cur != NULL || !st.empty()){
if(cur != NULL){
st.push(cur);
cur = cur->left;
}
else{
cur = st.top();
st.pop();
if(pre != NULL){
result = min(result, cur->val - pre->val);
}
pre = cur;
cur = cur->right;
}
}
return result;
}
};
leetcode 501.二叉搜索树中的众数
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
普通二叉树做法:
对于一般的二叉树,最直观的方法一定是把这个树都遍历了,用map统计频率,把频率排个序,最后取前面高频的元素的集合。
具体步骤如下:
这个树都遍历了,用map统计频率
这里采用前序遍历(使用任何遍历法均可,总之就是要对二叉树从头到尾遍历一遍),代码如下:
void searchBST(TreeNode* root, unordered_map& map){
if(root == NULL) return;
map[root->val]++; // 中
searchBST(root->left, map); // 左
searchBST(root->right, map); // 右
}
把统计的出来的出现频率(即map中的value)排个序
C++中如果使用std::map或者std::multimap可以对key排序,但不能对value排序。所以要把map转化数组即vector,再进行排序,当然vector里面放的也是pair
sort()函数可以对给定区间所有元素进行排序。它有三个参数sort(begin, end, cmp),其中begin为指向待sort()的数组的第一个元素的指针,end为指向待sort()的数组的最后一个元素的下一个位置的指针,cmp参数为排序准则,cmp参数可以不写,如果不写的话,默认从小到大进行排序。如果我们想从大到小排序可以将cmp参数写为greater
代码如下:
bool static cmp(const pair& a, const pair& b){
return a.second > b.second // 从大到小排序
}
vector> 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++){
if(vec[i].second == vec[0].second)
result.push_back(vec[i].first);
else
break; // 不是众数直接break出for循环
}
整体代码如下:
/**
* 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:
bool static cmp(const pair& a, const pair& b){
return a.second > b.second;
}
void searchBST(TreeNode* cur, unordered_map& map){
if(cur == NULL) return;
map[cur->val]++;
searchBST(cur->left, map);
searchBST(cur->right, map);
}
public:
vector findMode(TreeNode* root) {
unordered_map map;
vector result;
if(root == NULL) return result;
searchBST(root, map);
vector> 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指针的技巧,本题也类似。
弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。
而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。
代码如下:
if (pre == NULL) { // 第一个节点
count = 1; // 频率为1
} else if (pre->val == cur->val) { // 与前一个节点数值相同
count++;
} else { // 与前一个节点数值不同
count = 1;
}
pre = cur; // 更新上一个节点
因为要求最大频率的元素集合(注意是集合,不是一个元素,可以有多个众数)所以应该是先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合。(因为众数有多个)。
但是这种方式遍历了两遍数组。
/*****
那么如何只遍历一遍呢?(使用下面的clear技巧)
/*****
如果 频率count 等于 maxCount(最大频率),当然要把这个元素加入到结果集中(以下代码为result数组),代码如下:
if (count == maxCount) { // 如果和最大值相同,放进result中
result.push_back(cur->val);
}
if (count > maxCount) { // 如果计数大于最大值
maxCount = count; // 更新最大频率
result.clear(); // 很关键的一步,不要忘记清空result,之前result里的元素都失效了
result.push_back(cur->val);
}
整体代码如下:
/**
* 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 = NULL;
vector result;
void searchBST(TreeNode* cur){
if(cur == NULL) return;
searchBST(cur->left);
if(pre == NULL){
count = 1;
}
else if(pre->val == cur->val){
count += 1;
}
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);
}
searchBST(cur->right);
return;
}
public:
vector findMode(TreeNode* root) {
searchBST(root);
return result;
}
};
迭代法
直接使用中序遍历的栈模拟递归,代码框架与上题一模一样,只需更改对“中”的处理过程即可:
/**
* 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 {
public:
vector findMode(TreeNode* root) {
stack st;
int maxCount = 0;
int count = 0;
vector result;
TreeNode* cur = root;
TreeNode* pre = NULL;
while(cur != NULL || !st.empty()){
if(cur != NULL){
st.push(cur);
cur = cur->left;
}
else{
cur = st.top();
st.pop();
if(pre == NULL){
count = 1;
}
else if(pre->val == cur->val){
count += 1;
}
else
count = 1;
if(count == maxCount)
result.push_back(cur->val);
if(count > maxCount){
maxCount = count;
result.clear();
result.push_back(cur->val);
}
pre = cur;
cur = cur->right;
}
}
return result;
}
};
leetcode 236.二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。那么二叉树如何可以自底向上查找呢?
回溯啊,二叉树回溯的过程就是从低到上。后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。 即情况一:
由于题目中说:所有 Node.val 互不相同,即不存在相同节点。判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。
容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)。 情况二:
其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。
因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。
递归三部曲:
确定递归函数的参数和返回值
需要递归函数返回值,来告诉我们是否找到节点q或者p,那么返回值为bool类型
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
确定终止条件
遇到空的话,因为树都是空了,所以返回空。
那么我们来说一说,如果 root == q,或者 root == p,说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点的处理逻辑,下面讲解。
代码如下:
if (root == q || root == p || root == NULL) return root;
确定单层递归的逻辑
值得注意的是本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。
本题需要遍历整棵树,当找到最近公共祖先时一路返回最上层即可。
先用left和right接住左子树和右子树的返回值,代码如下:
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
如果left 和 right都不为空,说明此时root就是最近公共节点。这个比较好理解
如果left为空,right不为空,就返回right,说明目标节点是通过right返回的,反之依然。
如图:
图中节点10的左子树返回null,右子树返回目标值7,那么此时节点10的处理逻辑就是把右子树的返回值(最近公共祖先7)返回上去。
如果left和right都为空,则返回left或者right都是可以的,也就是返回空。
逻辑如下:
if (left == NULL && right != NULL) return right;
else if (left != NULL && right == NULL) return left;
else { // (left == NULL && right == NULL)
return NULL;
}
整体代码:
/**
* 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) {
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;
}
}
};