【问题标题】:efficiently find kth smallest element in binary search tree?有效地找到二叉搜索树中的第 k 个最小元素?
【发布时间】:2018-11-29 00:16:23
【问题描述】:

我今天面临以下面试问题,我想出了下面提到的递归和迭代解决方案,但不知何故面试官不高兴。我不知道为什么。

给定一棵二叉搜索树,找出其中第 k 个最小的元素。

有没有更好的方法以递归或迭代的方式解决这个问题?

  /*****************************************************
   *
   * Kth Smallest Recursive
   *
   ******************************************************/
  public int kthSmallestRecursive(TreeNode root, int k) {
    int count = countNodes(root.left);
    if (k <= count) {
      return kthSmallestRecursive(root.left, k);
    } else if (k > count + 1) {
      return kthSmallestRecursive(root.right, k - 1 - count);
    }

    return root.data;
  }

  public int countNodes(TreeNode n) {
    if (n == null)
      return 0;

    return 1 + countNodes(n.left) + countNodes(n.right);
  }

  /*****************************************************
   *
   * Kth Smallest Iterative
   *
   ******************************************************/

  public int kthSmallestIterative(TreeNode root, int k) {
    Stack<TreeNode> st = new Stack<>();

    while (root != null) {
      st.push(root);
      root = root.left;
    }

    while (k != 0) {
      TreeNode n = st.pop();
      k--;
      if (k == 0)
        return n.data;
      TreeNode right = n.right;
      while (right != null) {
        st.push(right);
        right = right.left;
      }
    }

    return -1;
  }

我提到了上述解决方案的复杂性为 O(depth of node),即 O(log n)。

我的迭代版本占用了额外的空间。有没有什么办法不用多余的空间?

【问题讨论】:

  • 你当然不能比 log n 做得更好。他们有没有提到任何具体的事情?
  • 你怎么知道他不开心?
  • 打一通countNodes 需要多长时间?计算树中的所有节点(或树中的一半节点)需要多长时间?这不是 O(log n)。此外,您不必要地多次查看相同的节点 - 您计算它们,然后递归,然后再次计算它们,然后递归,等等。
  • 您只需要中序遍历中的第 k 个元素。您的迭代版本似乎就是这样做的。递归版本效率不高。
  • @Dukeling 所以我的迭代版本很好,只有递归版本有问题,对吗?因为我多次检查相同的节点你能帮我理解为什么它不是 O(log n)。

标签: java algorithm data-structures binary-search-tree


【解决方案1】:

首先,我怀疑它是否需要 O(logn)。普通二叉搜索树至少为 O(n)。

对于vanilla bst,您可以进行迭代或递归,两者都是模拟相同的忽略器遍历过程,具有最差的时间复杂度 O(n) 和空间复杂度 O(logn)(请注意,即使是递归解决方案也可能需要 O(logn)堆栈空间)。

但是,如果您可以稍微更改数据结构,则可以加快速度或使用更少的空间:

  1. 如果您使用二叉搜索树,每个节点记录其下的总节点数,您可以将其视为排序数组并使用 O(logn)时间和 O(1) 空间。

  2. 如果您使用的二叉搜索树的每个节点都包含父指针和状态指示符,您可以采用与普通解决方案相同的方式遍历树。在途中,您标记节点是否为 a。未遍历 2. 已遍历其左节点 3. 已遍历两个节点。那么你可以在 O(n) 时间和 O(1) 空间内完成。

【讨论】:

  • 第三个选项我们不能改变数据结构并且需要找到它,那么以最佳方式递归和迭代的复杂性是多少?
  • 嗨,约翰。上面提到了两个选项:对于香草 bst,这是我们能做的最好的。
  • 嗨 Nicholas,这是查找某个元素,而不是第 k 个最大或最小的元素。
【解决方案2】:
 public int printInorder(Node node, int k) 
    { 
        if (node == null || k <= 0) //Stop traversing once you found the k-th smallest element
            return k; 

        /* first recur on left child */
        k = printInorder(node.left, k); 

        k--;
        if(k == 0) {  
            System.out.print(node.key);
        }

        /* now recur on right child */
        return printInorder(node.right, k);
    } 

在递归方法的countNodes() 方法中,您正在遍历每个节点的树以进行计数。 countNodes() 操作是 O(n) 并且您正在对 log(n) 个节点执行操作。所以你的递归解决方案的复杂性顺序是O(n^2),而我上面提到的方法在 O(n) 中解决。

【讨论】:

  • 但是到目前为止我的递归解决方案为 o(n^2) 时间复杂度和 O(1) 空间复杂度?
  • @john 是的。对不起这个错误。在最坏的情况下,您的解决方案将采用 O(n2),而空间采用 O(1)。我的立场是正确的。
  • @john: my recursive solution [has …] O(1) space complexity? 仅当您忽略 O(n) 调用堆栈保持节点时才需要重新访问。
猜你喜欢
  • 1970-01-01
  • 2011-01-20
  • 1970-01-01
  • 1970-01-01
  • 2011-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-06
相关资源
最近更新 更多