230. 二叉搜索树中第K小的元素

230. 二叉搜索树中第K小的元素

 230. 二叉搜索树中第K小的元素

Category Difficulty Likes Dislikes
algorithms Medium (75.81%) 691 -

给定一个二叉搜索树的根节点 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

进阶: 如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?

函数签名:

func kthSmallest(root *TreeNode, k int)

分析

中序遍历

因为BST的中序遍历是有序的,所以只需要做中序遍历即可。

func kthSmallest(root *TreeNode, k int) int {
	res := -1
	var dfs func(*TreeNode)
	dfs = func(root *TreeNode) {
		if root == nil {
			return
		}

		dfs(root.Left)
		k--
		if k == 0 {
			res = root.Val
			return
		}
		dfs(root.Right)
	}
	dfs(root)
	return res
}

写成迭代版:

func kthSmallest(root *TreeNode, k int) int {
    if root == nil {
        return -1
    }

    stk := []*TreeNode{root}
    for len(stk) > 0 || root != nil {
        for root != nil {
            stk = append(stk, root)
            root = root.Left
        }
        n := len(stk)
        root = stk[n-1]
        stk = stk[:n-1]
        k--
        if k == 0 {
            return root.Val
        }
        root = root.Right
    }
    
    return -1
}

时间复杂度:O(h+k),空间复杂度:O(h)。其中 h 指树的高度。

进阶问题

如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?

需要记录每个节点作为根的子树的节点数,在增删过程中除了维护 BST 的特性,还需维护每个子树的节点数。

参考如下:

type MyBst struct {
	root *TreeNode
	size map[*TreeNode]int // 维护以每个结点为根结点的子树的结点数
}

// 统计以 node 为根结点的子树的结点数
func (t *MyBst) count(node *TreeNode) int {
	if node == nil {
		return 0
	}
	t.size[node] = 1 + t.count(node.Left) + t.count(node.Right)
	return t.size[node]
}

// 返回二叉搜索树中第 k 小的元素
func (t *MyBst) kthSmallest(k int) int {
	for node := t.root; node != nil && k > 0; {
		leftSize := t.size[node.Left]
		if leftSize == k-1 {
			return node.Val
		}

		if leftSize < k-1 {
			node = node.Right
			k -= leftSize + 1
		} else {
			node = node.Left
		}
	}

	return -1
}

func kthSmallest(root *TreeNode, k int) int {
	tree := &MyBst{root, map[*TreeNode]int{}}
	tree.count(root)
	return tree.kthSmallest(k)
}

这样除了预处理,每次查询的时间复杂度是 O(h)

当然,如果树非常不平衡,h 会接近 n。所以这个优化还不够,需要用平衡的 BST 来代替一般的BST。略。