描述
给定一棵结点数为n 二叉搜索树,请找出其中的第 k 小的TreeNode结点值。
1.返回第k小的节点值即可
2.不能查找的情况,如二叉树为空,则返回-1,或者k大于n等等,也返回-1
3.保证n个节点的值不一样
数据范围: 0 \le n \le10000≤n≤1000,0 \le k \le10000≤k≤1000,树上每个结点的值满足0 \le val \le 10000≤val≤1000
进阶:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)
如输入{5,3,7,2,4,6,8},3时,二叉树{5,3,7,2,4,6,8}如下图所示:
该二叉树所有节点按结点值升序排列后可得[2,3,4,5,6,7,8],所以第3个结点的结点值为4,故返回对应结点值为4的结点即可。
我的思路
返回第k小的,那可不就是从最左节点中序遍历到的第k个数吗?这个简单,我可有思路。
很好,也错了,我就说,怎么一个中等题这么简单
int ans;
public int KthNode (TreeNode proot, int k) {
// write code here
//不能查找的情况,如二叉树为空,则返回-1,或者k大于n等等,也返回-1
int i = 0;
if (proot == null)
return -1;
travel(proot,i,k);
if (ans == 0)
return -1;
return ans;
}
private void travel(TreeNode root,int i,int k) {
if (root == null)
return;
//左
travel(root.left,i + 1,k);
//中
if (i == k)
ans = root.val;
//右
travel(root.right,i + 1,k);
}
这个错我就懒得看,反正整个逻辑就是错的,这个i操作应该是从遍历到最左节点开始计数的。而不是从头结点遍历下去这样计数的。所以做了第二次修改:
int ans;
public int KthNode (TreeNode proot, int k) {
// write code here
//不能查找的情况,如二叉树为空,则返回-1,或者k大于n等等,也返回-1
int i = 1;//因为方法传过去就是最左节点了,所以初始化的值直接为1
if (proot == null)
return -1;
LeftMost(proot);
travel(leftNode,i,k);
if (ans == 0)
return -1;
return ans;
}
private void travel(TreeNode root,int k,int i) {
if (root == null)
return;
//左
travel(root.left,k,i + 1);
//这个是不能这样计数的,得遍历到最左节点才是第一个数
//中
/*if (root.left == null)//希望在第一次遍历到最左端的时候执行这行代码
i = 1;*/
//这样肯定是不行的,得换一种思路
if (i == k)
ans = root.val;
//右
travel(root.right,k,i + 1);
}
//如何返回最左节点呢
TreeNode leftNode;
private void LeftMost(TreeNode root){
if (root.left == null) {
leftNode = root;
return;
}
LeftMost(root.left);
}
但这次代码的问题在于取到的最左节点是空的,左右指针都没有指向,再放入递归遍历的循环没办法回溯和向后取值了。所以这种写法也是错的。写的有点烦,用了一个比较暴力的方法先解出来了,等等看看别人解决的思路。
int ans;
List<Integer> list = new LinkedList<>();
public int KthNode (TreeNode proot, int k) {
if (k <= 0)
return -1;
// write code here
//不能查找的情况,如二叉树为空,则返回-1,或者k大于n等等,也返回-1
int i = 1;//因为方法传过去就是最左节点了,所以初始化的值直接为1
if (proot == null)
return -1;
// LeftMost(proot);
travel(proot);
for (int c: list){
if (i == k)
ans = c;
i++;
}
if (k >= i)
return -1;
return ans;
}
private void travel(TreeNode root) {
if (root == null)
return;
//左
travel(root.left);
//这个是不能这样计数的,得遍历到最左节点才是第一个数
//中
/*if (root.left == null)//希望在第一次遍历到最左端的时候执行这行代码
i = 1;*/
//这样肯定是不行的,得换一种思路
list.add(root.val);
//右
travel(root.right);
}
看了一下别人的题解,都快忘记了优先队列了,不过要注意优先队列的默认大根堆,如果我们要使用小根堆,就得重写比较器。具体的步骤和思想在注释里了。
public int KthNode (TreeNode proot, int k) {
if (k <= 0)
return -1;
if (proot == null)
return -1;
//这里用另一种方法,我居然都忘记了
PriorityQueue<Integer> pq = new PriorityQueue<>((a,b)->b - a);
//PriorityQueue<>((a, b) -> b - a)表示优先级安装最大优先,默认是最小优先(小根堆)
//在这里设置一个大根堆,如果有数传进来了就会按照大小排列,到时候要取第k个数直接弹出这个数就好,
// 第k小也是第k大(前k个元素中最大的),到时候直接返回堆顶就好
//看了别人的题解,发现写法会更好一些,学习了
/*
* 他的思想是使用K个元素的,用另外一个队列来承接节点,正常遍历,如果这个数值是前k个就放入
* 如果这个数小于大根堆堆顶就放进去,把栈顶弹出。以此类推,最后弹出堆顶就是我们要取得数了
* */
int i = 1;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(proot);//先把第一个节点放进去
while (!queue.isEmpty()){
TreeNode root = queue.poll();
if (pq.size() < k)
pq.add(root.val);
else if (root.val < pq.peek()){
pq.poll();
pq.add(root.val);
}
if (root.left != null) {
queue.add(root.left);
i++;
}
if (root.right != null) {
queue.add(root.right);
i++;
}
}
if (k > i)
return -1;
return pq.peek();
}
还有最后一种方法,就是我最开始想用但是没能成功的方法:利用搜索二叉树的性质,用中序遍历拿出这个数。没能实现,主要是有个边界条件很烦,不想看了,我好笨,有空再看看吧。