题目描述(中等难度)

230. Kth Smallest Element in a BST - 图1

给一个二叉搜索树,找到树中第 k 小的树。二叉搜索树的定义如下:

  1. 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  2. 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  3. 任意节点的左、右子树也分别为二叉查找树;
  4. 没有键值相等的节点。

思路分析

通过前边 98 题99 题 以及 108 题 的洗礼,看到二叉搜索树,应该会立刻想到它的一个性质,它的中序遍历输出的是一个升序数组。知道了这个,这道题就很简单了,只需要把中序遍历的第 k 个元素返回即可。

解法一 中序遍历

说到中序遍历,94 题 已经讨论过了,总共介绍了三种解法,大家可以过去看一下,这里的话,直接在之前的基础上做修改了。

总体上,我们只需要增加两个变量 numresnum 记录中序遍历已经输出的元素个数,当 num == k 的时候,我们只需要将当前元素保存到 res 中,然后返回即可。

下边分享下三种遍历方式的解法,供参考。

递归法。

  1. int num = 0;
  2. int res;
  3. public int kthSmallest(TreeNode root, int k) {
  4. inorderTraversal(root, k);
  5. return res;
  6. }
  7. private void inorderTraversal(TreeNode node, int k) {
  8. if (node == null) {
  9. return;
  10. }
  11. inorderTraversal(node.left, k);
  12. num++;
  13. if (num == k) {
  14. res = node.val;
  15. return;
  16. }
  17. inorderTraversal(node.right, k);
  18. }

递归改写,压栈法。

  1. public int kthSmallest(TreeNode root, int k) {
  2. Stack<TreeNode> stack = new Stack<>();
  3. int num = 0;
  4. int res = -1;
  5. TreeNode cur = root;
  6. while (cur != null || !stack.isEmpty()) {
  7. // 节点不为空一直压栈
  8. while (cur != null) {
  9. stack.push(cur);
  10. cur = cur.left; // 考虑左子树
  11. }
  12. // 节点为空,就出栈
  13. cur = stack.pop();
  14. // 当前值加入
  15. num++;
  16. if (num == k) {
  17. res = cur.val;
  18. break;
  19. }
  20. // 考虑右子树
  21. cur = cur.right;
  22. }
  23. return res;
  24. }

常数空间复杂度的 Morris 遍历,94 题 对 Morris 遍历有详细的解释。

  1. public int kthSmallest(TreeNode root, int k) {
  2. TreeNode cur = root;
  3. int num = 0;
  4. int res = -1;
  5. while (cur != null) {
  6. // 情况 1
  7. if (cur.left == null) {
  8. num++;
  9. if (num == k) {
  10. res = cur.val;
  11. break;
  12. }
  13. cur = cur.right;
  14. } else {
  15. // 找左子树最右边的节点
  16. TreeNode pre = cur.left;
  17. while (pre.right != null && pre.right != cur) {
  18. pre = pre.right;
  19. }
  20. // 情况 2.1
  21. if (pre.right == null) {
  22. pre.right = cur;
  23. cur = cur.left;
  24. }
  25. // 情况 2.2
  26. if (pre.right == cur) {
  27. pre.right = null; // 这里可以恢复为 null
  28. num++;
  29. if (num == k) {
  30. res = cur.val;
  31. break;
  32. }
  33. cur = cur.right;
  34. }
  35. }
  36. }
  37. return res;
  38. }

可以看到,三种解法都是一样的,我们只是在中序遍历输出的时候,记录了已经输出的个数而已。

解法二 分治法

如果不知道解法一中二叉搜索树的性质,用分治法也可以做,分享 这里 的解法。

我们只需要先计算左子树的节点个数,记为 n,然后有三种情况。

n1 等于 k,那就说明当前根节点就是我们要找的。

n1 小于 k,那就说明第 k 小的数一定在右子树中,我们只需要递归的在右子树中寻找第 k - n - 1 小的数即可。

n1 大于 k,那就说明第 k 小个数一定在左子树中,我们只需要递归的在左子树中寻找第 k 小的数即可。

  1. public int kthSmallest(TreeNode root, int k) {
  2. int n = nodeCount(root.left);
  3. if(n + 1 == k) {
  4. return root.val;
  5. } else if (n + 1 < k) {
  6. return kthSmallest(root.right, k - n - 1);
  7. } else {
  8. return kthSmallest(root.left, k);
  9. }
  10. }
  11. private int nodeCount(TreeNode root) {
  12. if(root == null) {
  13. return 0;
  14. }
  15. return 1 + nodeCount(root.left) + nodeCount(root.right);
  16. }

解法一的前提就是需要知道二分查找树的中序遍历是升序数组,问题就转换成中序遍历求解了。解法二的话,属于通用的解法,分治法,思路很棒。

windliang wechat

添加好友一起进步~

如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情