当前位置: 首页 > 知识库问答 >
问题:

Java:如何在BST中找到距离目标最近的k值?

邴英毅
2023-03-14

我正在研究如何在BST中找到最接近目标的k值,并遇到了以下规则实现:

给定非空的二元搜索树和目标值,在BST中查找距离目标最近的k个值。

注:

给定的目标值是浮点。您可以假设k始终有效,即:k≤ 节点总数。保证BST中只有一组最接近目标的唯一k值。假设BST是平衡的。

实现的想法是:

比较离目标最近的节点的前辈和后辈,我们可以使用两个堆栈来跟踪前辈和后辈,然后像我们在合并排序中所做的那样,我们比较并选择离目标最近的一个并将其放入结果列表。众所周知,按顺序html" target="_blank">遍历为我们提供了排序的前辈,而按顺序反向遍历为我们提供了排序的后辈。

代码:

import java.util.*;

class TreeNode {
    int val;
    TreeNode left, right;
    
    TreeNode(int x) { 
        val = x; 
    }
}


public class ClosestBSTValueII {
    List<Integer> closestKValues(TreeNode root, double target, int k) {
          List<Integer> res = new ArrayList<>();

          Stack<Integer> s1 = new Stack<>(); // predecessors
          
          Stack<Integer> s2 = new Stack<>(); // successors

          inorder(root, target, false, s1);
          inorder(root, target, true, s2);
          
          while (k-- > 0) {
            if (s1.isEmpty()) {
              res.add(s2.pop());
            } else if (s2.isEmpty()) {
              res.add(s1.pop());
            } else if (Math.abs(s1.peek() - target) < Math.abs(s2.peek() - target)) {
              res.add(s1.pop());
            } else {
              res.add(s2.pop());
            }
          }
          
          return res;
        }

    // inorder traversal
    void inorder(TreeNode root, double target, boolean reverse, Stack<Integer> stack) {
      
      if (root == null) {
          return;
      }

      inorder(reverse ? root.right : root.left, target, reverse, stack);
      // early terminate, no need to traverse the whole tree
      if ((reverse && root.val <= target) || (!reverse && root.val > target)) {
          return;
      }
      // track the value of current node
      stack.push(root.val);
      inorder(reverse ? root.left : root.right, target, reverse, stack);
    }
    
    public static void main(String args[]) {
        ClosestBSTValueII cv = new ClosestBSTValueII();
        
        TreeNode root = new TreeNode(53);
        root.left = new TreeNode(30);
        root.left.left = new TreeNode(20);
        root.left.right = new TreeNode(42);
        root.right = new TreeNode(90);
        root.right.right = new TreeNode(100);
        
        System.out.println(cv.closestKValues(root, 40, 2));
    }
}

我的问题是,有两个堆栈的原因是什么?有序是一个好的方法吗?每个的目的是什么?用一个堆栈遍历它难道还不够吗?

有一个反向布尔值的意义是什么,比如对于顺序(反向?…) ?如果<代码>if((相反

提前向您表示感谢,并将接受回答/支持投票。

共有2个答案

巢烨
2023-03-14

之所以需要两个堆栈,是因为必须在两个方向遍历树,并且必须将每个堆栈的当前值与正在搜索的值进行比较(最终可能会有k个值大于搜索值,或者k/2大于k/2,k/2小于k/2)。

我认为应该使用树节点堆栈,而不是整数堆栈;您可以避免递归

更新时间:

我在算法中看到了两个阶段:

1)在树中找到最接近的值,这将同时构建初始堆栈。

2)复制堆栈,向后移动一个元素,这将给你第二个堆栈;然后最多迭代k次:看看每个堆栈顶部的两个元素中哪个最接近搜索到的值,将其添加到结果列表中,并向前或向后移动堆栈。

更新2:一点代码

public static List<Integer> closest(TreeNode root, int val, int k) {
    Stack<TreeNode> right = locate(root, val);
    Stack<TreeNode> left = new Stack<>();
    left.addAll(right);
    moveLeft(left);
    List<Integer> result = new ArrayList<>();
    for (int i = 0; i < k; ++i) {
        if (left.isEmpty()) {
            if (right.isEmpty()) {
                break;
            }
            result.add(right.peek().val);
            moveRight(right);
        } else if (right.isEmpty()) {
            result.add(left.peek().val);
            moveLeft(left);
        } else {
            int lval = left.peek().val;
            int rval = right.peek().val;
            if (Math.abs(val-lval) < Math.abs(val-rval)) {
                result.add(lval);
                moveLeft(left);
            } else {
                result.add(rval);
                moveRight(right);
            }
        }
    }
    return result;
}

private static Stack<TreeNode> locate(TreeNode p, int val) {
    Stack<TreeNode> stack = new Stack<>();
    while (p != null) {
        stack.push(p);
        if (val < p.val) {
            p = p.left;
        } else {
            p = p.right;
        }
    }
    return stack;
}

private static void moveLeft(Stack<TreeNode> stack) {
    if (!stack.isEmpty()) {
        TreeNode p = stack.peek().left;
        if (p != null) {
            do {
                stack.push(p);
                p = p.right;
            } while (p != null);
        } else {
            do {
                p = stack.pop();
            } while (!stack.isEmpty() && stack.peek().left == p);
        }
    }
}

private static void moveRight(Stack<TreeNode> stack) {
    if (!stack.isEmpty()) {
        TreeNode p = stack.peek().right;
        if (p != null) {
            do {
                stack.push(p);
                p = p.left;
            } while (p != null);
        } else {
            do {
                p = stack.pop();
            } while (!stack.isEmpty() && stack.peek().right == p);
        }
    }
}

更新3

用一个堆栈遍历它还不够吗?

有一个反向布尔值的意义是什么,比如对于inorder(反向?…);?如果是if((相反

我不知道你从哪里得到你在问题中给出的解决方案,但是总结一下,它构建了两个整数列表,一个按直线顺序,一个按倒序。当达到搜索值时,它会“提前”终止。这个解决方案听起来效率很低,因为它需要遍历整个树。当然,我的要好得多,而且它符合给定的规则。

濮俊美
2023-03-14

您找到的算法的思想非常简单。它们只是从应该插入目标的位置按顺序遍历树。它们使用两个堆栈来存储前置和后续。以树为例:

     5
    / \
   3   9
  / \   \
 2   4   11 

让目标为8。当所有按序调用完成时,堆栈将是:s1={2,3,4,5}s2={11,9}。如您所见,s1包含目标的所有前辈,以及它的所有后继者。此外,两个堆栈的排序方式是,每个堆栈的顶部比堆栈中的所有其他值更接近目标值。因此,我们可以很容易地找到最接近的值,只需始终比较堆栈的顶部,并弹出最接近的值,直到我们有了值。他们算法的运行时间是O(n)

现在关于你的问题。我不知道如何有效地使用唯一的堆栈来实现这个算法。堆栈的问题是我们只能访问它的顶部。但是用一个数组来实现算法非常容易。让我们只做通常的树的按顺序遍历。对于我的例子,我们将得到:arr={2, 3, 4, 5, 9, 11}。然后让我们将lr索引放在两边最接近目标值的位置:l=3r=4arr[l]=5arr[r]=9)。剩下的就是始终比较arr[l]arr[r]并选择要添加到结果中的内容(绝对相同,与两个堆栈一样)。该算法还需要O(n)操作。

在我看来,他们解决这个问题的方法在代码中似乎有点难以理解,尽管它相当优雅。

我想用另一个运行时间介绍另一种解决问题的方法。这种算法将花费O(k*logn)时间,这对于小k更好,对于大的更差。比以前的算法。

还可以将指向父节点的指针存储在TreeNode类中。然后,我们可以在O(logn)时间内(如果您不知道怎么做的话)轻松地找到树中任何节点的前置节点或后续节点。因此,让我们首先在树中找到目标的前一个和后一个(无需进行任何遍历!)。然后执行与堆栈相同的操作:比较前置器\后续器,选择最近的一个,对于最近的,转到其前置器\后续器。

我希望,我回答了你的问题,你理解我的解释。如果没有,请随时提问!

 类似资料:
  • 我已经实现了一个函数,在运行K-Means聚类算法后,找到距离每个质心最近的数据点。我想知道是否有一个函数可以让我找到距离每个质心最近的M个点。

  • 问题: 这是LeetCode的问题: 给定一个整数数组,返回所有对之间的 k 个最小距离。一对(A,B)的距离定义为A和B之间的绝对差。 例子: 我的问题 我用一个简单的方法解决了它O(n^2),基本上我找到所有的距离并对其进行排序,然后找到第k个最小的。现在这是一个更好的解决方案。这不是我的代码,我在leetcode的讨论论坛上找到了它。但是我很难理解代码的关键部分。 下面的代码基本上是在做一个

  • 假设iris数据有3个唯一的类值。 如何使用Weka API获取这些值?我能找到的最接近的是numDistinctValues(),我目前使用它作为 然而,这只给出了不同数量的类,即3。我想得到实际值,即类标签“Iris setosa,Iris versicolor,Iris virginica”。 我们可以使用Instances()找到不同的类值,方法是提取每个实例对应的所有类标签,然后从中找到

  • 我遇到了以下leetcode问题,我对一些人用来解决它的方法有一个问题。问题是:给定一个非空二叉查找树和一个目标值,在BST中找到最接近目标的k个值。 注意:给定的目标值是浮点。 您可以假设k始终有效,即:k≤总节点。 保证BST中只有一组最接近目标的唯一k值。 所以,有些人所做的是,他们在保持k大小的最近元素队列的同时,按顺序遍历。在顺序遍历过程中,如果发现某个元素比队列中的第一个节点更接近目标

  • 我希望使用Geopandas/Shapely实现ArcPy Generate Near表的等效功能。我对Geopandas和Shapely非常陌生,并且已经开发出一种有效的方法,但我想知道是否有更有效的方法。 我有两个点文件数据集——人口普查街区中心和餐馆。我在寻找每个人口普查街区形心到最近餐馆的距离。同一家餐厅是多个街区最近的餐厅,没有任何限制。 对我来说,这变得有点复杂的原因是因为Geopan

  • 我正在尝试使用jgraph T解决一个链接预测问题。我正在根据两个节点的邻居计算两个节点之间的相似性。每个节点都有一些属性。计算变得太多了,因为一些节点有大约700个邻居,而我有4500个这样的节点。我有谁对的700K边,我计算相似性。 现在,我不想使用节点的所有邻居,我只想使用每个节点的k个最近邻居来计算一对节点之间的相似度。我可以根据边共享的两个节点的属性数,或节点之间长度为n的最短路径数等,