一、题目描述
给定一个二叉搜索树的根节点 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
提示:
- 树中的节点数为
n
。 1 <= k <= n <= 10^4
0 <= Node.val <= 10^4
二、解题思路
由于给定的树是一个二叉搜索树(BST),它的性质是左子树的所有节点值小于根节点值,右子树的所有节点值大于根节点值。因此,可以通过中序遍历(In-order Traversal)的方式来访问树的所有节点,这样可以得到一个递增的节点值序列。
我们可以使用一个计数器来记录已经访问过的节点数,当计数器的值等于 k 时,当前访问的节点值即为第 k 小的元素。
以下是具体的步骤:
- 初始化一个计数器 count 为 0。
- 进行中序遍历。
- 每访问一个节点,计数器 count 加 1。
- 当 count 等于 k 时,返回当前节点的值。
三、具体代码
class Solution {
private int count = 0;
private int result = 0;
public int kthSmallest(TreeNode root, int k) {
inOrderTraversal(root, k);
return result;
}
private void inOrderTraversal(TreeNode node, int k) {
if (node == null || count >= k) {
return;
}
// 遍历左子树
inOrderTraversal(node.left, k);
// 访问当前节点
count++;
if (count == k) {
result = node.val;
return;
}
// 遍历右子树
inOrderTraversal(node.right, k);
}
}
在这段代码中,inOrderTraversal
方法是递归地进行中序遍历。当 count
等于 k
时,我们找到了第 k 小的元素,并将其值赋给 result
。由于我们在找到第 k 小的元素后立即返回,所以不会继续不必要的遍历,这提高了算法的效率。
四、时间复杂度和空间复杂度
1. 时间复杂度
-
时间复杂度:O(n),其中 n 是二叉搜索树中的节点数量。
- 对于每个节点,我们只访问一次,因为一旦找到第 k 小的元素,我们就会停止遍历。
- 在最坏的情况下,如果第 k 小的元素是树中的最后一个元素,我们需要遍历所有的 n 个节点。
-
最好情况时间复杂度:O(k),如果第 k 小的元素位于二叉搜索树的前 k 个节点中,我们只需要遍历 k 个节点。
2. 空间复杂度
-
空间复杂度:O(h),其中 h 是二叉搜索树的高度。
- 这是因为在递归过程中,我们需要维护一个递归栈。在最坏的情况下,递归栈的大小与树的高度相同,即树退化成一条链表时。
- 由于二叉搜索树可能退化成链表,所以空间复杂度取决于树的高度,而不是节点数量。
-
最坏情况空间复杂度:O(n),当树完全不平衡,即树的高度等于节点数量 n。
-
平均情况空间复杂度:O(log n),在平衡的二叉搜索树中,树的高度大约是 log n,因此递归栈的空间复杂度是 O(log n)。
这些分析假设树的结构是未知的,可能是不平衡的,也可能是完全平衡的。在实际情况中,如果已知树是平衡的,那么平均情况的空间复杂度更可能是适用的。
五、总结知识点
-
二叉树遍历:
- 代码实现了二叉树的中序遍历(In-order Traversal),这是一种深度优先搜索(DFS)的特例。
- 中序遍历二叉搜索树会按照节点的递增顺序访问所有节点。
-
递归:
inOrderTraversal
方法是递归的,它调用自身来遍历左子树和右子树。- 递归是处理树结构数据的一种常用方法。
-
二叉搜索树性质:
- 二叉搜索树的左子树中的所有节点的值小于根节点的值,右子树中的所有节点的值大于根节点的值。
- 利用这一性质,中序遍历可以按顺序访问所有节点。
-
计数器:
- 使用一个私有变量
count
来记录已经访问过的节点数量。 - 当
count
等于k
时,意味着找到了第 k 小的元素。
- 使用一个私有变量
-
全局变量:
- 使用私有变量
result
来存储找到的第 k 小的元素值。 - 由于
result
在递归过程中被修改,因此它必须是类的实例变量,而不是局部变量。
- 使用私有变量
-
递归终止条件:
- 当访问到
null
节点或者已经找到第 k 小的元素时(即count >= k
),递归会终止。
- 当访问到
-
返回值:
kthSmallest
方法返回找到的第 k 小的元素值。inOrderTraversal
方法没有返回值,因为它的作用是通过修改实例变量result
来存储结果。
-
递归栈:
- 虽然代码中没有直接使用栈数据结构,但递归调用实际上是通过调用栈来实现的。
- 每次递归调用都会在调用栈上添加一个新的帧。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。