二叉搜索树的第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个节点。
- 先复习下最原始的二叉树中序遍历框架
-
- 中序遍历-递归实现
//中序遍历,递归
//递归
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();
}
}
- 再看看这一题怎么改造原始的递归框架
先考虑用递归的方式求解
- 首先,除了参数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的引用,要么定义一个全局变量用于计数。