题目
230. 二叉搜索树中第K小的元素
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
示例 1:
输入:root = [3,1,4,null,2], k = 1
输出:1
示例 2:
输入:root = [5,3,6,2,4,null,null,1], k = 3
输出:3
思路1:
中序遍历刚好就是一个升序序列,那我们中序遍历,取第k个值即可。
这里用迭代法实现二叉搜索树的中序遍历。
代码如下:
/**
* @param {TreeNode} root
* @param {number} k
* @return {number}
*/
var kthSmallest = function(root, k) {
let stack = [];
let res = [];
while(k && (stack.length>0 || root)){
while(root){
stack.push(root);
root = root.left;
}
root = stack.pop();
res.push(root.val);
k--;
root = root.right;
}
return res[res.length-1]
};
复杂度分析
时间复杂度:O(H+k),其中 H是树的高度。在开始遍历之前,我们需要 O(H)到达叶结点。当树是平衡树时,时间复杂度取得最小值 O(log H + k);当树是线性树(树中每个结点都只有一个子结点或没有子结点)时,时间复杂度取得最大值 O(H+k)。
空间复杂度:O(H),栈中最多需要存储 H 个元素。当树是平衡树时,空间复杂度取得最小值 O(log N);当树是线性树时,空间复杂度取得最大值 O(N)。
思路2:
如果我们知道了当前节点root的左子树的节点数量,其实就可以做以下三种情况的判断:
- 左子树的节点数left小于k-1,那么说明第k个树在右子树上,那么我们可以让root = root.right,并且k = k-(left + 1);
- 左子树的节点数left等于k-1,那么说明第k个数正好是当前的root
- 左子树的节点数left大于k-1,那么说明第k个数在左子树上,那么我们让root = root.left,继续向下搜索
这里,我们需要做几件事情
- 创建一个hash表,提前遍历整个数,然后将节点和他对应的子树的节点数量放入hash表
- 从根节点开始遍历整个二叉树,然后根据上面分析的判断,向下一直搜索,直到left = k-1
代码如下:
var kthSmallest = function(root, k) {
const bst = new MyBst(root);
return bst.kthSmallest(k);
};
class MyBst {
constructor(root) {
this.root = root;
this.nodeNum = new Map();
this.countNodeNum(root);
}
// 返回二叉搜索树中第k小的元素
kthSmallest(k) {
let node = this.root;
while (node != null) {
const left = this.getNodeNum(node.left);
if (left < k - 1) {
node = node.right;
k -= left + 1;
} else if (left === k - 1) {
break;
} else {
node = node.left;
}
}
return node.val;
}
// 统计以node为根结点的子树的结点数
countNodeNum(node) {
if (node == null) {
return 0;
}
this.nodeNum.set(node, 1 + this.countNodeNum(node.left) + this.countNodeNum(node.right));
return this.nodeNum.get(node);
}
// 获取以node为根结点的子树的结点数
getNodeNum(node) {
return this.nodeNum.get(node) || 0;
}
}
复杂度分析:
时间复杂度:O(n),n为节点数,因为我们需要提前遍历整个二叉树,去做节点和节点的子节点数的映射。
空间复杂度:O(n),需要去将所有节点的映射都存起来。
思路3:
平衡二叉搜索树。这个老铁们去leetcode自行看题解吧