1、题目描述
https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/
2、解法(对应的思维)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int rank = 0;//记录排名节点
int res=0;//定义结果的记录
public int kthSmallest(TreeNode root, int k) {
//算法思维——二叉搜索树,无非是利用其中序遍历得到的序列是有序的,同时根据其左子树小于右子树的特性来改良算法的效率
//选择第k小的树,我们可以利用中序遍历,遍历的次数到达k,说明当前访问的节点的值就是第k小/第k大的数。
if(root==null) return res;
kthSmallest(root.left,k);
//
rank++;
if(rank == k){
//找到dik小的数直接返回
res = root.val;
return res;//提前返回
}
kthSmallest(root.right,k);
return res;
}
}
分析:时间复杂度上看,子问题个数为n,每个问题的计算时间为O(1),总的时间复杂度为O(n),n为节点个数。实际上我们只需要计算k个节点,k值不太大的情况下,算法较快。
算法思维——重要
算法思维——二叉搜索树,无非是利用其中序遍历得到的序列是有序的,同时根据其左子树小于右子树的特性来改良算法的效率。比如我们的二叉搜索树的操作效率一般最好的是O(logN),利用了 它左子树小于右子树,同时节点记录了哪些信息,来帮助我们减少计算。
上述题目的优化思路
BST 性质是非常牛逼的,像红黑树这种改良的自平衡 BST,增删查改都是O(logN)的复杂度,让你算一个第k小元素,时间复杂度竟然要O(N),有点低效了。
看了labuladong的算法小小抄,我知道了它是如何进行优化的:因为我们要寻找第k小的节点的值,如果我们要达到对数级别的复杂度,关键于每个节点得知道它排第几(m),这样 ,我们在寻找第k小的数时,可以根据其当前节点的排名与我们要找的排名作比较。基于中序遍历的基础:
- m==k,显然我们找到了第k排名的元素,返回即可。
- k<m,说明我们接下来应该去左子树找,所以去左子树搜索第k个元素。
- k>m,说明我们接下来应该去右子树找,所以去右子树搜索第k-m-1个元素。
这样我们就可以实现O(logN)的时间复杂度了,至于如何知道每个节点的排名?这就是我们之前说的,需要在二叉树节点中维护额外信息。每个节点需要记录,以自己为根的这棵二叉树有多少个节点。
也就是说,我们TreeNode中的字段应该如下:
class TreeNode {
int val;
// 以该节点为根的树的节点总数
int size;
TreeNode left;
TreeNode right;
}
有了size字段,外加 BST 节点左小右大的性质,对于每个节点node就可以通过node.left推导出node的排名,从而做到我们刚才说到的对数级算法。当然,size字段需要在增删元素的时候需要被正确维护,力扣提供的TreeNode是没有size这个字段的,所以我们这道题就只能利用 BST 中序遍历的特性实现了,但是我们上面说到的优化思路是 BST 的常见操作,还是有必要理解的。