二叉搜索树的第k个结点

leetcode 230. 二叉搜索树中第K小的元素

https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/
给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。
在这里插入图片描述
说明:
你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。

提供的函数接口如下:

/**
 * 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 kthSmallest(TreeNode* root, int k) {

    }
};

思路:主要是根据二叉树的中序遍历来求第k个节点。

  1. 先复习下最原始的二叉树中序遍历框架
    • 中序遍历-递归实现
//中序遍历,递归
//递归
void midOrderRecursion(TreeNode* root){
    if(root==nullptr)    return;
    midOrderRecursion(root->left);
    cout<<root->value<<endl;
    midOrderRecursion(root->right);
}
    • 中序遍历-非递归实现

利用栈模拟递归过程,思路如下:
对于给定的二叉树根结点 root,
(1)若其左孩子不为空,循环将 root及 root左子树中的所有结点的左孩子入栈;
(2)取栈顶元素 cur,访问 cur 并将 cur 出栈。然后对 cur 的右子结点进行步骤(1)那样的处理;
(3)重复(1)和(2)的操作,直到 cur 为空且栈为空。

void midOrderStack(TreeNode* root){
    if(root==nullptr)    return;
    stack<TreeNode*> Stack;
    TreeNode* cur=root;
    
    while(!Stack.empty()||cur!=nullptr){//这里循环条件变为栈为空或cur非nullptr
        while(cur){//循环将root及root左子树的所有结点的左孩子入栈
            Stack.push(cur);
            cur=cur->left;
        }
        cur=Stack.top();
        cout<<cur->value<<endl;
        cur=cur->right;
        Stack.pop();
    }
}
  1. 再看看这一题怎么改造原始的递归框架

先考虑用递归的方式求解

  • 首先,除了参数root之外,肯定得传入参数k作为形参
  • 其次,中序遍历肯定是在 midOrderRecursion(root->left);和 midOrderRecursion(root->right);的中间做文章
void midOrderRecursion(TreeNode* root){
    if(root==nullptr)    return;
    midOrderRecursion(root->left);
    //中序遍历,在这里做文章
    midOrderRecursion(root->right);
}
  • base case,当root==nullptr,应该做啥?

先看看题目原始的函数声明如下,若直接在题目给定的这个函数接口下做中序递归,你会发现
if(root==nullptr) 时,你不知道返回什么,所以我们另外定义一个辅助函数来做中序递归。

int kthSmallest(TreeNode* root, int k) {
    if(root==nullptr)
    //不知道这里返回什么     
}

所以添加一个辅助函数,

void inOrderRecursion(TreeNode* root,int k){
    //base case
    if(!root||k<1)    return ;
    inOrderRecursion(root->left,k);
   //在这里做文章
    inOrderRecursion(root->right,k);

     return ;
    }

定义了这个辅助函数后,处理base case的返回值容易了,但是你发现,怎么把第k个节点的值传递出去呢?

这里有一种很简单的做法
就是用一个序列容器,比如vector,保存中序遍历的所有结果,最后返回这个容器中的第k个元素,
我们只用将原始中序遍历框架中 cout<< root->value<<endl;的地方,改为将节点尾插到容器中即可
简单是简单,就是有点费内存~~

这里可以定义一个外部变量result,在遍历到第k个节点时,将值赋给result,然后,很容易会写出下面的错误代码

//错误代码
    int result=0;
    void inOrderRecursion(TreeNode* root,int k){
        //base case
        if(!root||k<1)    return ;

        inOrderRecursion(root->left,k);
        if(0==--k){
             result=root->val;
             return ;
        }
        inOrderRecursion(root->right,k);

    }

    int kthSmallest(TreeNode* root, int k) {
        //if(!root) 不知道这里返回啥就不管了,反正inOrderRecursion会处理nullptr的情况
        inOrderRecursion(root,k);
        return result;
    }

仔细想想可以发现,上面的错误在于,当在中序遍历到中,k自减后,整个递归函数从底向上返回到上一层时,自减后的值并没有传递到上一层
举例:
输入
[7,5,8,1,6,null,10]
k=3
时,这个函数的递归过程如下图,可知最后result会等于10,并不是我们想象中的6。
在这里插入图片描述
为了解决上面的问题,我们要想办法把底层递归函数自减k后的结果传递到上一层

一种做法是,传参时传入k的引用。

    int result=0;
    //注意这里传入的是int& k
    void inOrderRecursion(TreeNode* root,int& k){
        //base case
        if(!root||k<1)    return ;

        inOrderRecursion(root->left,k);
        if(0==--k){
             result=root->val;
             return ;
        }
        inOrderRecursion(root->right,k);

        return ;
    }

    int kthSmallest(TreeNode* root, int k) {
        //if(!root) 不知道这里返回啥就不管了,反正inOrderRecursion会处理nullptr的情况
        inOrderRecursion(root,k);
        return result;
    }

这个函数的递归过程如下图,可知最后result会等于6。
在这里插入图片描述
另一种做法是,我们在定义一个额外的外部变量num,每次在都对这个外部变量自增,当num自增到k时,赋值给外部变量result。当然,归根结底,都是把底层递归函数计数变化后的结果传递到上一层

    int result=0;
    int num=0;
    void inOrderRecursion(TreeNode* root,int k){
        //base case
        if(!root||k<1)    return ;

        inOrderRecursion(root->left,k);
        if(++num==k){
             result=root->val;
             return ;
        }

        inOrderRecursion(root->right,k);

        return ;
    }

    int kthSmallest(TreeNode* root, int k) {
        //if(!root) 不知道这里返回啥就不管了,反正inOrderRecursion会处理nullptr的情况
        inOrderRecursion(root,k);
        return result;
    }

在这里插入图片描述

有了上面的递归解法,然后可以利用栈,将上面的解法改为非递归的形式

    int result=0;
    void inOrderStack(TreeNode* root,int& k){
        //base case
        if(!root||k<1)    return ;

        stack<TreeNode*> Mystack;
        TreeNode* cur=root;

        while(cur||!Mystack.empty()){
            //左子树一路到底
            while(cur){
                Mystack.push(cur);
                cur=cur->left;
            }
            //左子树最低节点出栈
            cur=Mystack.top();
            if(--k==0){
                result=cur->val;
                break;
            }
            Mystack.pop();

            //对左子树最低节点的右子树做同样的事情
            cur=cur->right;      
        }
       

    }

    int kthSmallest(TreeNode* root, int k) {
        //if(!root) 不知道这里返回啥就不管了,反正inOrderRecursion会处理nullptr的情况
        inOrderStack(root,k);
        return result;
    }

剑指offer_面试题54

剑指 Offer 54. 二叉搜索树的第k大节点
给定一棵二叉搜索树,请找出其中第k大的节点。

  struct TreeNode {
      int val;
      TreeNode *left;
      TreeNode *right;
      TreeNode(int x) : val(x), left(NULL), right(NULL) {}

跟上面的题一样,用上面的代码改改,就可以解决这个问题。

注意是返回第k大的节点,而不是第k小的节点,所以得改变一下中序遍历,改为“右子树-根-左子树“

class Solution {
public:
    int count=0;
    int res=0;
    void kthLargestCore(TreeNode* root,int k){
        if(!root) return ;
        //先右子树
        kthLargest(root->right,k);
        //再根
        count++;
        if(count==k){
            res=root->val;
            return;
        }
        //后左子树
        kthLargest(root->left,k);    
    }
    
    //中序遍历
    int kthLargest(TreeNode* root, int k) {
        kthLargestCore(root,k);
        return res; 
    }
};

总结

要想把底层递归函数计数变化后的结果传递到上一层,要么传递得是k的引用,要么定义一个全局变量用于计数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值