Binary Lifting
Goal
Find N-th ancestor of a given tree node in O(logN) time.
Tree Presentation
You are given a tree with n nodes numbered from 0 to n-1 in the form of a parent array, where parent[i] is the parent of node i.
The root of the tree is node 0.
Key Idea
- Precompute every node’s
2^nparents, n = 0, 1, 2, … Thus it’s O(1) to find any node’s 1, 2, 4, 8, 16 -th parent. - Any
kcan be written in binary form. E.g.k = 10 = 0b1010 = 2^3 + 2^1 - A node’s
kth parent is same as itsk1th parent’sk2th parent, wherek = k1 + k2. E.g. A node’s 10th parent is its 8th parent’s 2nd parent.
Key Implementation
Precompute all nodes’ 2^n parents
- Definition:
dp[i][j]: nodei’s2^jparent - node
i’s’2^jparent = its2^(j-1)parent’s2^(j-1)parent. - Transition function:
dp[i][j] = dp[dp[i][j-1]][j-1]. dp[i][j]depends ondp[?][j-1]
Time complexity
Precompute walk through all n nodes and compute log(n) parents. Time complexity is O(NlgN)
Space complexity
dp matrix requires space of O(NlgN)
1 | |
Get kth ancestor
Time complexity
K is decomposed to sum of 2^?. There’re at most lg(K) of such terms.
Time complexity is O(lgN)
1 | |
Another Binary Lifting
With a smart jump algorithm, smart means we can bound its time complexity to O(lgN), then we can get rid of the dp[i][j] array and just use a single jump array of size n. This reduce the space complexity to O(N) from O(NlgN)
Key Idea
jump[n]stores node i’s next jumpdepth[n]array stores node i’s current depth- jump if you can, else move one step forward
1
2
3
4
5
6
7
8
9
10public int getKthAncestor(int node, int k) { while (depth[node] > k) { if (depth[jump[node]] < k) { node = parent[node]; } else { node = jump[node]; } } return node; } - Precompute
jump[n].1
2
3
4
5
6
7
8
9// compute jump for a given node if (parent.depth - parent.jump.depth == parent.jump.depth - parent.jump.jump.depth) { // if parent have two equal length jump forward // then jump to cover the two equal length jump node.jump = parent.jump.jump; } else { // make one step jump node.jump = parent; }
Why & Who
- A good intuitive explanation with drawings. Check it out here.
- Original paper from Eugene W. MYERS in 1983: An applicative random-access stack.
Additional Thoughts - Pow(x, n)
Goal
Compute x^n in O(lgN) time.
Comparison
- In binary lifting, 5 jumps = 4 jumps + 1 jumps
- In Pow(x, n),
x^5 = x^4 * x = (x^2) * (x^2) * x1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// recursive solution public double Pow(double x, int n) { // 0^0 = 1 // 0^-2 = infinity if (n == 0) { return 1; } if (n < 0) { return 1 / (myPow(x, -(n+1)) * x); //return 1 / myPow(x, -n); //stackoverflow for 1^-2147483648 } if (x == 0) { return 0; } if (n % 2 == 0) { return myPow(x * x, n/2); } else { return x * myPow(x, n-1); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// iterative solution public double Pow(double x, int n) { if (n == 0) { return 1; } if (n < 0) { return 1 / (myPow(x, -(n+1)) * x); //return 1 / myPow(x, -n); //stackoverflow for 1^-2147483648 } if (x == 0) { return 0; } double answer = 1.0; while (n > 0) { if (n % 2 == 1) { answer = answer * x; } n = n / 2; x = x * x; } return answer; }