Following the hint, I use a predecessor stack and successor stack. I do a logn traversal to initialize them until I reach the null node. Then I use the getPredecessor and getSuccessor method to pop k closest nodes and update the stacks.

Time complexity is O(klogn), since k BST traversals are needed and each is bounded by O(logn) time. Note that it is not O(logn + k) which is the time complexity for k closest numbers in a linear array.

Space complexity is O(klogn), since each traversal brings O(logn) new nodes to the stack.

```
public class Solution {
public List<Integer> closestKValues(TreeNode root, double target, int k) {
List<Integer> result = new LinkedList<Integer>();
// populate the predecessor and successor stacks
Stack<TreeNode> pred = new Stack<TreeNode>();
Stack<TreeNode> succ = new Stack<TreeNode>();
TreeNode curr = root;
while (curr != null) {
if (target <= curr.val) {
succ.push(curr);
curr = curr.left;
} else {
pred.push(curr);
curr = curr.right;
}
}
while (k > 0) {
if (pred.empty() && succ.empty()) {
break;
} else if (pred.empty()) {
result.add( getSuccessor(succ) );
} else if (succ.empty()) {
result.add( getPredecessor(pred) );
} else if (Math.abs(target - pred.peek().val) < Math.abs(target - succ.peek().val)) {
result.add( getPredecessor(pred) );
} else {
result.add( getSuccessor(succ) );
}
k--;
}
return result;
}
private int getPredecessor(Stack<TreeNode> st) {
TreeNode popped = st.pop();
TreeNode p = popped.left;
while (p != null) {
st.push(p);
p = p.right;
}
return popped.val;
}
private int getSuccessor(Stack<TreeNode> st) {
TreeNode popped = st.pop();
TreeNode p = popped.right;
while (p != null) {
st.push(p);
p = p.left;
}
return popped.val;
}
}
```