【LeetCode & 剑指offer刷题】树题16:Kth Smallest Element in a BST
【LeetCode & 剑指offer 刷题笔记】目录(持续更新中...)
Kth Smallest Element in a BST
Given a binary search tree, write a function kthSmallest to find the kth smallest element in it.
Note:
You may assume k is always valid, 1 ≤ k ≤ BST's total elements.
Example 1:
Input: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
Output: 1
Example 2:
Input: root = [5,3,6,2,4,null,null,1], k = 3
5
/ \
3 6
/ \
2 4
/
1
Output: 3
Follow up:
What if the BST is modified (insert/delete operations) often and you need to find the kth smallest frequently? How would you optimize the kthSmallest routine?
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
/*
方法一:中序遍历递归法
BST中序遍历之后为从小到大排列
中序遍历递归法,不是最优的,因为是遍历完之后在给出的结果(可用迭代法进行改进,参考题目 Validate Binary Search Tree)
注:可在递归中加入判断进行减枝
*/
class Solution
{
public:
int kthSmallest(TreeNode* root, int k)
{
vector<int> nodes;
inorder(root, nodes);
return nodes[k-1];
}
void inorder(TreeNode* root, vector<int>& nodes)
{
if(root == nullptr) return; //递归的出口
inorder(root->left, nodes);
nodes.push_back(root->val);
inorder(root->right, nodes);
}
};
/*
方法二:中序遍历迭代法
在遍历的过程中统计数量
*/
class Solution
{
public:
int kthSmallest(TreeNode* root, int k)
{
int cnt = 0;
stack<TreeNode*> s;
TreeNode *p = root;
while (!s.empty() || p)
{
if(p) //左结点不为空时
{
s.push(p); //入栈
p = p->left; //指向下一个左结点
}
else //左结点为空时
{
p = s.top();
cnt++; //统计数目(遍历到了要访问的父结点)
if (cnt == k) return p->val;
s.pop();
p = p->right; //指向右结点
}
}
return 0;
}
};
/*
方法三:分治法
首先计算出左子树的结点个数总和cnt,
如果k小于等于左子树结点总和cnt,说明第k小的元素在左子树中,直接对左子结点调用递归即可。
如果k大于cnt+1,说明目标值在右子树中,对右子结点调用递归函数
如果k等于cnt+1,则当前结点即为所求
*/
class Solution
{
public:
int kthSmallest(TreeNode* root, int k)
{
int cnt = count(root->left);
if (k <= cnt)
{
return kthSmallest(root->left, k);
}
else if (k > cnt + 1)
{
return kthSmallest(root->right, k - cnt - 1); //注意此处变为k - (cnt-1)
}
else
return root->val;
}
int count(TreeNode* node)
{
if (!node) return 0;
return 1 + count(node->left) + count(node->right);
}
};
/*
Follow up: 假设该BST被修改的很频繁,而且查找第k小元素的操作也很频繁,问我们如何优化
方法:改进方法三
修改原树结点的结构,使其保存包括当前结点和其左右子树所有结点的个数,
这样我们使用的时候就可以快速得到任何左子树结点总数来帮我们快速定位目标值了
分析:
对于查找很频繁的情况,由于保存了结点个数,每次查找时,不需要递归统计结点个数,这样可以节省大量时间
对于修改很频繁的情况,插入或者删除某个结点时,需更新其所有祖先结点,所以该方法并不太适用于修改很频繁的情况
*/
class Solution
{
private:
struct MyTreeNode
{
int val;
int count;
MyTreeNode *left;
MyTreeNode *right;
MyTreeNode(int x) : val(x), count(1), left(NULL), right(NULL) {}
};
public:
int kthSmallest(TreeNode* root, int k)
{
MyTreeNode *node = build(root);
return helper(node, k);
}
MyTreeNode* build(TreeNode* root)
{
if (!root)
return NULL;
else
{
MyTreeNode *node = new MyTreeNode(root->val); //count在构造函数中被初始化为1
node->left = build(root->left);
node->right = build(root->right);
if (node->left) node->count += node->left->count; //统计数量
if (node->right) node->count += node->right->count;
return node;
}
}
int helper(MyTreeNode* node, int k)
{
if (node->left)
{
int cnt = node->left->count; //左结点存储了当前结点左子树的所有结点个数
if (k <= cnt)
return helper(node->left, k);
else if (k > cnt + 1)
return helper(node->right, k - 1 - cnt);
else
return node->val;
}
else
{
if (k == 1)
return node->val;
else
return helper(node->right, k - 1);
}
}
};