我正在研究如何在BST中找到最接近目标的k值,并且遵循以下规则实施:
给定非空二进制搜索树和目标值,在BST中找到最接近目标的k值。
注意:
给定目标值是浮点数。 您可以假设k始终有效,即:k≤总节点数。 保证BST中只有一组唯一的k值最接近目标。假设BST是平衡的。
实施的想法是:
将最近节点的前辈和后继者与目标进行比较,我们可以使用两个堆栈来跟踪前辈和后继者,然后就像我们在合并排序中所做的那样,我们比较并选择最接近目标的一个并放置它到结果列表。如我们所知,inorder遍历为我们提供了已排序的前导,而反向顺序遍历为我们提供了已排序的后继。
代码:
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));
}
}
我的问题是,有两个堆栈的原因是什么,以及如何有序的方法?每个人的目的是什么?不会用一个堆栈遍历它就足够了吗?
具有reverse
布尔值的重点是什么,例如inorder(reverse ? ...);
?在if ((reverse && root.val <= target) || (!reverse && root.val > target))
的情况下,为什么要提前终止?
提前感谢您,并接受回答/投票。
答案 0 :(得分:0)
你需要两个堆栈的原因是你必须在两个方向上遍历树,你必须将每个堆栈的当前值与你正在搜索的值进行比较(你可能最终得到的k值大于搜索到的值)值,或k / 2更大,k / 2更低)。
我认为你应该使用堆栈的TreeNodes而不是堆栈的Integer;你可以避免递归。
更新:
我在算法中看到两个阶段:
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
不会用一个堆栈遍历它就足够了吗?
具有反向布尔值的重点是什么,例如for inorder(反向?...);?在if((反向&amp;&amp; root.val。)的情况下 &lt; = target)|| (!reverse&amp;&amp; root.val&gt; target)),为什么要终止 早?
我不知道你在哪里得到了你提出的解决方案,但总而言之,它建立了两个整数列表,一个是直接顺序,一个是相反的顺序。当达到搜索值时,它会“提前”终止。这种解决方案听起来非常低效,因为它需要遍历整棵树。当然,我的情况要好得多,而且符合既定规则。
答案 1 :(得分:0)
您找到的算法的想法非常简单。它们只是从应该插入target
的地方按顺序遍历树。他们使用两个堆栈来存储前辈和后继者。让我们以树为例:
5
/ \
3 9
/ \ \
2 4 11
让目标为8
。完成所有inorder
方法调用后,堆栈将为:s1 = {2, 3, 4, 5}
,s2 = {11, 9}
。如您所见,s1
包含target
和s2
的所有后续版本的所有前任。此外,两个堆栈都以某种方式排序,每个堆栈的top
比堆栈中的所有其他值更接近target
。因此,我们可以轻松找到k
最接近的值,只需始终比较堆栈的顶部,然后弹出最接近的值,直到我们得到k
值。算法的运行时间为O(n)
。
现在谈谈你的问题。我不知道如何有效地使用唯一的堆栈实现此算法。堆栈的问题是我们只能访问它的顶部。但是用一个数组实现算法非常容易。让我们通常按顺序遍历树。对于我的例子,我们将获得:arr = {2, 3, 4, 5, 9, 11}
。然后,我们可以将l
和r
索引放置在距离两边最近的目标值的位置:l = 3
,r = 4
(arr[l] = 5
,arr[r] = 9
)。剩下的就是始终比较arr[l]
和arr[r]
并选择要添加到结果中的内容(绝对相同,就像两个堆栈一样)。这个算法也需要O(n)
次操作。
他们对这个问题的解决方法在我看来有点难以理解,尽管它相当优雅。
我想在另一个运行时间引入另一种解决问题的方法。该算法将花费O(k*logn)
时间,这对于小k
更好,对于更大的算法比以前的算法更差。
让我们在TreeNode
类中存储一个指向父节点的指针。然后我们可以在O(logn)
时间(if you don't know how)中轻松找到树中任何节点的前导或后继。所以,让我们首先在树的前任和目标的后继中找到(不做任何遍历!)。然后执行与堆栈相同的操作:比较前一个\ successor,选择最接近的一个,并选择最接近它的前任\ successor。
我希望,我回答了你的问题并理解了我的解释。如果没有,请随时询问!