【从零开始的刷题之旅】二叉搜索树(02):好用的性质

在了解了二叉搜索树的增删改查之后,我们继续用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就是最近公共祖先。

首先来看一眼在二叉树中我们见过的题目

leetcode 236 二叉树的最近公共祖先

image-20220408164301298

寻找两个节点的最近公共祖先,当时代码是这样写的

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的下面。

leetcode 1676 二叉树的最近公共祖先IV

这次对问题进行了一个升级,不再是求两个节点,而是给出了一个节点的列表,求它们的最近公共祖先

当然需要修改的地方不是很多,只需要用一个set来存放所有元素,然后查找是否存在即可。

leetcode 1644 二叉树的最近公共祖先II

这道题和前面两道的区别在于,虽然给出了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;
    }

最后进入正题,二叉搜索树的最近公共祖先

leetcode 235 二叉搜索树的最近公共祖先

前面我们说了如何找到最近公共祖先,就是要求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;
        
    }

本题和前两题一样,都是默认存在最近公共祖先(这点很重要)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值