Binary Trees · 33 of 35
Kth Ancestor of node in a Binary tree
Find the great great grandparent k steps above a person in a family tree by counting down while the good news travels up.
- Simple way
- time O(n)
room O(n) - Clever way
- time O(n)
room O(h)
The problem, simply
You get a binary tree with different values, a target value, and a number k. The 1st ancestor of a node is its parent, the 2nd is its grandparent, and so on. Give back the value of the target's k-th ancestor, or -1 if the target does not have that many ancestors.
A story to remember it
Picture it
Target 8 and k = 2. Going up from 8: 5 is 1 step up, 2 is 2 steps up. The answer is 2.
- checking
- answer
- path
Examples
Grandparent
- In
tree = [1, 2, 3, 4, 5, 6, 7, null, null, 8, 9], target = 8, k = 2- Out
2
8's parent is 5 and 5's parent is 2.
Parent
- In
same tree, target = 9, k = 1- Out
5
The 1st ancestor is the parent.
Too far up
- In
same tree, target = 8, k = 4- Out
-1
8 has only three ancestors: 5, 2 and 1. There is no 4th.
The root
- In
same tree, target = 1, k = 1- Out
-1
The root has no parent at all.
Way 1: the simple way
Write down every person's parent first. Then start at the target and step to the parent k times.
- Walk the tree with a waiting line (a queue), and for each child store
parent[child] = node. Remember the target node when you pass it. - Starting from the target, step to its parent
ktimes. - If you fall off the top before
ksteps, answer-1.
The parent book after one walk. From 8, look up 5, then look up 2. Two lookups, so the answer is 2.
- looking now
- checking
- answer
- path
import java.util.*;
class Solution {
public int kthAncestor(TreeNode root, int k, int target) {
if (root == null) return -1;
Map<TreeNode, TreeNode> parent = new HashMap<>();
Queue<TreeNode> line = new ArrayDeque<>();
line.add(root);
TreeNode start = null;
while (!line.isEmpty()) {
TreeNode node = line.poll();
if (node.val == target) start = node;
if (node.left != null) { parent.put(node.left, node); line.add(node.left); }
if (node.right != null) { parent.put(node.right, node); line.add(node.right); }
}
TreeNode cur = start;
for (int step = 0; step < k && cur != null; step++) {
cur = parent.get(cur); // one step up; the root has no parent
}
return cur == null ? -1 : cur.val;
}
}How long it takes: one walk to fill the book, then k steps, O(n). Extra room: a parent entry for every node, O(n). The book is worth it if you will ask many questions on the same tree.
Way 2: the clever way
Search for the target with recursion. When a node hears "found it" from one of its sides, it is one step above the target, so it takes 1 off k. The node where k reaches 0 is the answer. No book, no list.
- If the node is empty, return "not found".
- If the node is the target, return "found".
- Ask the left side; if it says no, ask the right side.
- If either side said "found": subtract 1 from
k. Ifkis now 0, this node is the answer. Return "found". - Otherwise return "not found".
Count down while the news climbs up
01 / 08
Look for 8, starting at the root. k is 2, so we want the grandparent.
- k
- 2
- answer
- none yet
- looking now
- checking
- answer
- path
- ignored
class Solution {
private int k;
private int answer;
public int kthAncestor(TreeNode root, int k, int target) {
this.k = k;
this.answer = -1; // stays -1 if k never reaches 0
find(root, target);
return answer;
}
// true when the target is somewhere under this node (or is this node)
private boolean find(TreeNode node, int target) {
if (node == null) return false;
if (node.val == target) return true;
if (find(node.left, target) || find(node.right, target)) {
k--; // I am one more step above the target
if (k == 0) answer = node.val;
return true;
}
return false;
}
}How long it takes: in the worst case we search the whole tree, O(n). Extra room: only the call stack, O(h).
Watch out for
- When
kis bigger than the depth,knever reaches 0. Make sure the answer starts as-1. ||stops early: once the left side finds the target, the right side is never searched. That is good, not a bug.- The target might not be in the tree. Both ways return
-1then, because no one ever hears "found".
What interviewers ask next
- Answer many queries fast. Use binary lifting: store each node's 1st, 2nd, 4th, 8th ancestor and so on. Then jump using the bits of
k, for examplek = 5is a jump of 4 and a jump of 1. Building takesO(n log n), each queryO(log n). - Do it without recursion. Use Way 1, or an iterative walk that keeps the current path in a list, then read
path.get(path.size() - 1 - k). - Find the lowest common ancestor. The same "report up" idea, but now news arrives from two places.
- Nodes have parent pointers already. Then step up
ktimes:O(k)time andO(1)room.
Remember this
Grown-up words: grandparents are ancestors, the parent book is a hash map from child to parent, shouting back up is returning from recursion, and jumping 1, 2, 4, 8 steps at once is binary lifting.