这一题,可以非完全的转换成为:给你一个排好序的数组,在里面找距离target最近的k个数。之所以可以这样转换,是因为对于一个bst来说,inorder可以给你一个排好序的数组
那给你一个排好序的数组,怎么求距离target最近的k个数呢?其实有两种办法。
1. 顺序遍历右移,保持一个size为k的window(因为距离target最近的k个数肯定是连续的)。找到一个临界点,这个临界点的定义是,window里最大的数(最右边)与target的距离开始大于最小的数和target的距离。那么解集就会在这个临界点的window或者右移之前的一个window出现。
2. 贪心法:保持一个头指针和尾指针,比较头指针和尾指针和target的距离,头指针距离大一点就往右移一格,反之则是尾指针往左移一格。直到头指针和尾指针中间有k个元素为止(包括头和尾指针)
2.b 贪心法:这是上面的做法的反向做法。做法2是从外往里收缩。而这个2.b是找到排好序的数组里找到距离target最近的元素的位置,然后用类似的原理外扩:头指针的距离近一些,就左移一格,否则则是尾指针右移一格。直到头尾指针中间有k个元素。
所以,其实,最朴实的一种做法就是先inorder 扫出一个数组来,然后,用上述三种方式的任意一种即可。
public List closestKValues(TreeNode root, double target, int k) {
return _closestKValues3(root, target, k);
}
private List _closestKValues3(TreeNode root, double target, int k) {
Stack treeStk = new Stack();
List inOrdered = new ArrayList();
while (!treeStk.isEmpty() || root != null) {
if (root != null) {
treeStk.push(root);
root = root.left;
} else {
root = treeStk.pop();
inOrdered.add(root.val);
root = root.right;
}
}
int head = 0, tail = inOrdered.size() - 1;
while (tail - head >= k) {
double headDist = Math.abs((double)inOrdered.get(head) - target);
double tailDist = Math.abs((double)inOrdered.get(tail) - target);
if (headDist > tailDist) {
head++;
} else {
tail--;
}
}
return inOrdered.subList(head, tail + 1);
}
其实还可以一边遍历一边确定k个数,用第一个方法那样。譬如说下面这段代码:
private List _closestKValues2(TreeNode root, double target, int k) {
Stack treeStack = new Stack();
LinkedList result = new LinkedList();
while (root != null || !treeStack.isEmpty()) {
if (root != null) {
treeStack.push(root);
root = root.left;
} else {
root = treeStack.pop();
if (result.size() < k) {
result.add(root.val);
} else if (Math.abs((double)result.getFirst() - target) > Math.abs((double)root.val - target)) {
result.removeFirst();
result.add(root.val);
} else {
break;
}
root = root.right;
}
}
return result;
}
那么,在balanced tree下实现小于O(n)的情况,到底是如何做到呢?复杂度是多少咧?
好,我能理解的范围内,是O(logn + k)。原理还是没有逃离上面的描述,是2.b的进阶做法。首先O(logn)找到距离target最近的数。因为是Balanced的,所以O(logn)是可以保证的(如果不是balanced的话,最坏的情况依旧是O(n))。难点就在于如何从中心往两边扩散。这就高端了,具体请参见:https://leetcode.com/problems/closest-binary-search-tree-value-ii/discuss/129442/Two-Stack-Iterators-in-Java-O(logN-+-K)。。。
咳咳。这确实挺高端的,它的做法是如何循环的从中间某个节点开始做inorder,关键点就在于如何build up stack。其实在那个链接里,只需要关注stackBuild的isSuc的情况即可。这就是正向in order(左中右)的方法。也就是如果在搜寻距离最近的点的过程里,往左走了,就把节点push进stack,否则就略过。原因很简单,在in order里,如果先往左走了,还需要往右走,但如果往右走了,就不需要再考虑往左走了。至于stackNext这个函数,其实就是一个tree iterator很常见的做法。就是把上面代码循环的部分拆解开来了而已。在链接里的两个函数里,isSuc表示的是正向inorder,否则是反向inOrder,也就是右中左,正向in order走的是ascending的排序序列,反向in order走的是descending的排序序列。所以当找到最接近的点,然后一边反向 in order走,越走越小,另一边正向 in order走,越走越大, 这样就可以达到2.b贪心法的效果。这样吧,给出自己写的代码,和上面link有不一样的哦。真的。。。
private List _closestKValues4(TreeNode root, double target, int k) {
Stack ascStk = buildStack(root, target, true);
Stack descStk = buildStack(root, target, false);
List result = new LinkedList();
while (result.size() < k) {
if (ascStk.isEmpty() ||
(!descStk.isEmpty() && Math.abs(ascStk.peek().val - target) >= Math.abs(descStk.peek().val - target))) {
result.add(descStk.peek().val);
iterateStack(descStk, false);
} else {
result.add(ascStk.peek().val);
iterateStack(ascStk, true);
}
}
return result;
}
private void iterateStack(Stack nodeStk, boolean asc) {
TreeNode node = nodeStk.pop();
node = asc ? node.right : node.left;
while (node != null) {
nodeStk.push(node);
node = asc ? node.left : node.right;
}
}
private Stack buildStack(TreeNode root, double target, boolean asc) {
Stack nodeStk = new Stack();
while (root != null) {
if (root.val >= target) {
if (asc) nodeStk.push(root);
root = root.left;
} else {
if (!asc) nodeStk.push(root);
root = root.right;
}
}
return nodeStk;
}