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?

Credits:
Special thanks to @ts for adding this problem and creating all test cases.

这又是一道关于二叉搜索树 Binary Search Tree 的题, LeetCode 中关于 BST 的题有 Validate Binary Search TreeRecover Binary Search TreeBinary Search Tree IteratorUnique Binary Search TreesUnique Binary Search Trees IIConvert Sorted Array to Binary Search Tree 和 Convert Sorted List to Binary Search Tree。那么这道题给的提示是让我们用 BST 的性质来解题,最重要的性质是就是左<根<右,如果用中序遍历所有的节点就会得到一个有序数组。所以解题的关键还是中序遍历啊。关于二叉树的中序遍历可以参见我之前的博客 Binary Tree Inorder Traversal,里面有很多种方法可以用,先来看一种非递归的方法,中序遍历最先遍历到的是最小的结点,只要用一个计数器,每遍历一个结点,计数器自增1,当计数器到达k时,返回当前结点值即可,参见代码如下:

解法一:

class Solution {
public:
int kthSmallest(TreeNode* root, int k) {
int cnt = ;
stack<TreeNode*> s;
TreeNode *p = root;
while (p || !s.empty()) {
while (p) {
s.push(p);
p = p->left;
}
p = s.top(); s.pop();
++cnt;
if (cnt == k) return p->val;
p = p->right;
}
return ;
}
};

当然,此题我们也可以用递归来解,还是利用中序遍历来解,代码如下:

解法二:

class Solution {
public:
int kthSmallest(TreeNode* root, int k) {
return kthSmallestDFS(root, k);
}
int kthSmallestDFS(TreeNode* root, int &k) {
if (!root) return -;
int val = kthSmallestDFS(root->left, k);
if (k == ) return val;
if (--k == ) return root->val;
return kthSmallestDFS(root->right, k);
}
};

再来看一种分治法的思路,由于 BST 的性质,可以快速定位出第k小的元素是在左子树还是右子树,首先计算出左子树的结点个数总和 cnt,如果k小于等于左子树结点总和 cnt,说明第k小的元素在左子树中,直接对左子结点调用递归即可。如果k大于 cnt+1,说明目标值在右子树中,对右子结点调用递归函数,注意此时的k应为 k-cnt-1,应为已经减少了 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 + ) {
return kthSmallest(root->right, k - cnt - );
}
return root->val;
}
int count(TreeNode* node) {
if (!node) return ;
return + count(node->left) + count(node->right);
}
};

这道题的 Follow up 中说假设该 BST 被修改的很频繁,而且查找第k小元素的操作也很频繁,问我们如何优化。其实最好的方法还是像上面的解法那样利用分治法来快速定位目标所在的位置,但是每个递归都遍历左子树所有结点来计算个数的操作并不高效,所以应该修改原树结点的结构,使其保存包括当前结点和其左右子树所有结点的个数,这样就可以快速得到任何左子树结点总数来快速定位目标值了。定义了新结点结构体,然后就要生成新树,还是用递归的方法生成新树,注意生成的结点的 count 值要累加其左右子结点的 count 值。然后在求第k小元素的函数中,先生成新的树,然后调用递归函数。在递归函数中,不能直接访问左子结点的 count 值,因为左子节结点不一定存在,所以要先判断,如果左子结点存在的话,那么跟上面解法的操作相同。如果不存在的话,当此时k为1的时候,直接返回当前结点值,否则就对右子结点调用递归函数,k自减1,参见代码如下:

解法四:

// Follow up
class Solution {
public:
struct MyTreeNode {
int val;
int count;
MyTreeNode *left;
MyTreeNode *right;
MyTreeNode(int x) : val(x), count(), left(NULL), right(NULL) {}
}; MyTreeNode* build(TreeNode* root) {
if (!root) return NULL;
MyTreeNode *node = new MyTreeNode(root->val);
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 kthSmallest(TreeNode* root, int k) {
MyTreeNode *node = build(root);
return helper(node, k);
} 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 + ) {
return helper(node->right, k - - cnt);
}
return node->val;
} else {
if (k == ) return node->val;
return helper(node->right, k - );
}
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/230

类似题目:

Binary Tree Inorder Traversal

Second Minimum Node In a Binary Tree

参考资料:

https://leetcode.com/problems/kth-smallest-element-in-a-bst/

https://leetcode.com/problems/kth-smallest-element-in-a-bst/discuss/63659/what-if-you-could-modify-the-bst-nodes-structure

https://leetcode.com/problems/kth-smallest-element-in-a-bst/discuss/63660/3-ways-implemented-in-JAVA-(Python)%3A-Binary-Search-in-order-iterative-and-recursive

https://leetcode.com/problems/kth-smallest-element-in-a-bst/discuss/63743/Java-divide-and-conquer-solution-considering-augmenting-tree-structure-for-the-follow-up

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Kth Smallest Element in a BST 二叉搜索树中的第K小的元素的更多相关文章

  1. [LeetCode] 230. Kth Smallest Element in a BST 二叉搜索树中的第K小的元素

    Given a binary search tree, write a function kthSmallest to find the kth smallest element in it. Not ...

  2. LeetCode 230 Kth Smallest Element in a BST 二叉搜索树中的第K个元素

    1.非递归解法 /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * ...

  3. 230 Kth Smallest Element in a BST 二叉搜索树中第K小的元素

    给定一个二叉搜索树,编写一个函数kthSmallest来查找其中第k个最小的元素. 注意:你可以假设k总是有效的,1≤ k ≤二叉搜索树元素个数. 进阶:如果经常修改二叉搜索树(插入/删除操作)并且你 ...

  4. [leetcode] 230. Kth Smallest Element in a BST 找出二叉搜索树中的第k小的元素

    题目大意 https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/ 230. Kth Smallest Elem ...

  5. Leetcode Kth Smallest Element in a BST

    Given a binary search tree, write a function kthSmallest to find the kth smallest element in it. Not ...

  6. [LeetCode] Insert into a Binary Search Tree 二叉搜索树中插入结点

    Given the root node of a binary search tree (BST) and a value to be inserted into the tree, insert t ...

  7. LeetCode Kth Smallest Element in a BST(数据结构)

    题意: 寻找一棵BST中的第k小的数. 思路: 递归比较方便. /** * Definition for a binary tree node. * struct TreeNode { * int v ...

  8. [LeetCode] Inorder Successor in BST 二叉搜索树中的中序后继节点

    Given a binary search tree and a node in it, find the in-order successor of that node in the BST. No ...

  9. [LeetCode] 285. Inorder Successor in BST 二叉搜索树中的中序后继节点

    Given a binary search tree and a node in it, find the in-order successor of that node in the BST. Th ...

随机推荐

  1. Elasticsearch的CRUD:REST与Java API

    CRUD(Create, Retrieve, Update, Delete)是数据库系统的四种基本操作,分别表示创建.查询.更改.删除,俗称"增删改查".Elasticsearch ...

  2. angularjs和ajax的结合使用 (二)

    今天我们来继续丰富上次的例子.我们来搞些 稍微复杂点的应用. 首先我们来加一个全选 的功能. 上一篇的例子里我们看到 分页时载入的是我们通过linq 查询自定义列 然后构建的匿名类 .使用这种EF框架 ...

  3. Entity Framework 数据库初始化的三种方法

    在数据库初始化产生时进行控制,有三个方法可以控制数据库初始化时的行为.分别为CreateDatabaseIfNotExists.DropCreateDatabaseIfModelChanges.Dro ...

  4. Sql Service 的job作业新建过程

    第一步:打开sql service 找到 SQL SERVER Agent 下的 jobs 如图: 注:如果没有找到,请查看你安装sql service 的版本   通过 select @@VERSI ...

  5. 如何在VS 2010中使用 VS2013的解决方案(转)

    今天要用VS2010打开VS2013,一直觉得VS2010到VS2012只是界面上扁平化的改变,平台工具集有改变但很大程度上可能向上兼容.在网上搜了一些文章,其中有一篇说到一个观点:        从 ...

  6. 《连载 | 物联网框架ServerSuperIO教程》-4.如开发一套设备驱动,同时支持串口和网络通讯。附:将来支持Windows 10 IOT

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...

  7. 面向对象设计模式纵横谈:Singelton单件模式(笔记记录)

       李建忠老师讲的<面向对象设计模式纵横谈>,早就看过了,现在有了时间重新整理一下,以前的博客[赛迪网]没有了,现在搬到博客园,重新过一遍,也便于以后浏览. 设计模式从不同的角度分类会得 ...

  8. [转载]C#深入分析委托与事件

    原文出处: 作者:风尘浪子 原文链接:http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html 同类链接:http://www.c ...

  9. div 模拟<select>事件

    IE7 下,不能够自定义<select>/<option>的样式,所以为了方便起见,用div可以进行模拟 <!doctype html> <html> ...

  10. react-native-vector-icons的简单使用,图片,按钮,标签视图,导航条

    ICONS是可以直接使用图片名, 就能加载图片的三方,使用很方便, 你不需要在工程文件夹里塞各种图片, 节省很多空间,下面就来看看怎么使用吧! 1. 首先打开terminal进入到我们的工程文件夹下, ...