在了解了二叉搜索树的增删改查之后,我们继续用BST的特性来解决一些题目。
排序性质
因为节点的“左大右小”性质,我们可以得到二叉搜索树的中序遍历是有序的。这一点也是解决很多题目的关键
第k小的元素
[leetcode 230 二叉搜索树中第k小元素](230. 二叉搜索树中第K小的元素 - 力扣(LeetCode) (leetcode-cn.com))
这道题要求我们求出二叉搜索树中第k小的元素,k从1开始。
我们可以想到,根据排序性质,第1个就是最小的元素。k可以当作BST顺序数组的下标,遍历后就可以找到该元素。
这里我们使用另一个方法,在不使用额外空间的条件下也可以实现。
k相当于第几个的标记,我们把这个标记带入到二叉搜索树的遍历当中。
在中序遍历时,计算当前的rank值(排序值),再与k做比较。
int rank=0;
int res=0;
void traverse(TreeNode*root,int k){
if(root==NULL)return;
traverse(root->left,k);
rank++;
if(k==rank){
res=root->val;
return;
}
traverse(root->right,k);
}
这里rank用来记录位置信息,且正好是从1开始的。
最后在主函数调用traverse即可。
累加树转换
还有一道非常经典的二叉搜索树题目
[leetcode 538 把二叉搜索树转换为累加树](538. 把二叉搜索树转换为累加树 - 力扣(LeetCode) (leetcode-cn.com))
累加树的特征非常明显,从最右边开始依次进行累加。而这个遍历顺序符合“右中左”,类似反过来的二叉搜索树。
既然BST中序遍历可以得到递增,那我们改变遍历顺序,先遍历右子树,就可以得到递减的序列了。
再将数值相加,最后就可以得到累加树。
int sum=0;
void traverse(TreeNode*root){
if(root==NULL)return;
traverse(root->right);//右
sum+=root->val;
root->val=sum;
traverse(root->left);//左
}
我们先将sum的值加上根节点的值,再将这个和赋给新的根节点。
最后在主函数中调用traverse即可。
前后指针的妙用
回顾上一篇中 验证二叉搜索树 这道题目,我们没有把遍历结果放入数组,而是使用pre指针巧妙地在遍历时解决比大小问题。
最小绝对差
再来看一道题
[leetcode 530 二叉搜索树中的最小绝对差](530. 二叉搜索树的最小绝对差 - 力扣(LeetCode) (leetcode-cn.com))
给定一个二叉搜索树,返回任意两个节点之间的最小差值。
首先二叉搜索树是有序的,所以最小差值肯定出现在两个相邻的节点之间。
我们可以使用中序遍历得到有序数组,然后遍历这个数组,求相邻的每一对之间的差值,得到最小差值。
当然,使用pre指针是更好的做法。
TreeNode* pre=NULL;
void traverse(TreeNode*root){
if(root==NULL)
return;
traverse(root->left);
if(pre!=NULL){
res=min(res,root->val-pre->val);
}
pre=root;
traverse(root->right);
}
这样在主函数中调用就可以得到res了。
众数
来看这一道题
[leetcode 501 二叉搜索树中的众数](501. 二叉搜索树中的众数 - 力扣(LeetCode) (leetcode-cn.com))
这是一颗非典型的二叉搜索树,左子树节点小于等于当前节点值,右子树节点大于当前节点值,带了一个等于条件。
找到所有树中出现频率最高的元素(有可能会有多个)
我们依旧利用排序的有序性,可以把排序得到的非降序数组拿出来,然后进行遍历。找到出现频率最高的那几个。
当然,如果使用pre指针可以省去这部分的空间,但需要明白这里的逻辑。
我们记录一个计算频率的count,在第一次(pre==NULL)的时候赋值为1,如果后面的节点值与前面相等就+1,并且每次更新最大值。
最大值也有一点技巧。如果与当前的count相等,就继续把这个值放入结果集。如果count比最大值大了,就要更新一下最大值,同时要注意结果集已经失效了,需要清空后再放进去。
int maxCount=0;
int count=0;
vector<int>res;
TreeNode* pre=NULL;
void traverse(TreeNode* root){
if(root==NULL)
return ;
traverse(root->left);
if(pre==NULL){
count=1;
}
else if(pre->val==root->val){
count++;
}else{
count=1;
}
pre=root;
if(count==maxCount){
res.push_back(root->val);
}
if(count>maxCount){
res.clear();
maxCount=count;
res.push_back(root->val);
}
traverse(root->right);
}
看上去比较长,但其实理清楚逻辑后就比较清楚了。
最近公共祖先
这里来讨论一类问题,二叉树/二叉搜索树的最近公共祖先
这类问题的核心思路是:
如果一棵树的左子树中有p,右子树中有q,那么root就是最近公共祖先。
首先来看一眼在二叉树中我们见过的题目
寻找两个节点的最近公共祖先,当时代码是这样写的
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==p||root==q||root==NULL)
return root;
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
if(left==NULL&&right!=NULL)
return right;
if(left!=NULL&&right==NULL)
return left;
if(left!=NULL&&right!=NULL)
return root;
return NULL;
}
这段代码还有一些小细节:
首先可以看出我们在前序的位置处理了根节点,也就是说我们先处理了根节点再去处理左右子树。然后在后序位置处理了得到的结果。
如果遇到结果直接返回,不需要再进行遍历。
而题目中说明了pq一定存在,这也是为什么遇到pq就可以直接返回的原因。
比如[5,4]的公共祖先是5,当遍历到5的时候就返回了,如果在右子树没有找到另一个节点,那么说明另一个节点一定在5的下面。
这次对问题进行了一个升级,不再是求两个节点,而是给出了一个节点的列表,求它们的最近公共祖先
当然需要修改的地方不是很多,只需要用一个set来存放所有元素,然后查找是否存在即可。
这道题和前面两道的区别在于,虽然给出了pq节点,但有可能不存在最近公共祖先。
也就不能使用直接返回root的方式了。
或者说,不能在前序处理根节点的时候就把节点返回。
所以我们可以改成后序处理。
bool findp=false;
bool findq=false;
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
TreeNode* res=help(root,p,q);
if(!findp||!findq){
return null;
}
return res;
}
TreeNode* help(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==NULL)
return NULL;
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
if(root==p||root==q){
if(root==p)
findp=true;
if(root==q)
findq=true;
return root;
}
if(left==NULL&&right!=NULL)
return right;
if(left!=NULL&&right==NULL)
return left;
if(left!=NULL&&right!=NULL)
return root;
return NULL;
}
最后进入正题,二叉搜索树的最近公共祖先
前面我们说了如何找到最近公共祖先,就是要求pq正好在当前root的左右子树上,或者是与当前root相等,那么root就是最近公共祖先。
而对于二叉搜索树来说,我们得到的序列是有序的。
这样就更好找了,只需要p->val<=root->val<=q->val就可以判断root是答案了。
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root->val>p->val&&root->val>q->val){
return lowestCommonAncestor(root->left,p,q);
}
else if(root->val<p->val&&root->val<q->val){
return lowestCommonAncestor(root->right,p,q);
}
else
return root;
}
本题和前两题一样,都是默认存在最近公共祖先(这点很重要)。