Given a tree with n nodes numbered from 0 to n - 1, rooted at node 0 and an array arr[], where arr[i] represents the parent of node i. For the root node, arr[0] = -1.
Also given a 2D array queries[][] of size q × 2, where each query is of the form [u, k].
For each query, find the k-th ancestor of node u, i.e., the node obtained after moving k times from u to its parent. If the k-th ancestor does not exist, return -1.
Return an array containing the answer for each query in the same order.
Examples:
Input: arr[] = [-1, 0, 0, 1, 1], queries[][] = [[4, 1], [3, 2], [4, 3]]
Output: [1, 0, -1]
Explanation:
The 1st ancestor of 4 is 1.
The 2nd ancestor of 6 is 0.
The 3rd ancestor of 4 does not exist.Input: arr[] = [-1, 0, 1, 0, 1, 1, 3, 3], queries[][] = [[5, 1], [5, 2], [2, 2], [7, 2]]
Output: [1, 0, 0, 0]
Explanation:
The 1st ancestor of 5 is 1.
The 2nd ancestor of 5 is 0.
The 2nd ancestor of 2 is 0.
The 2nd ancestor of 7 is 0.
Table of Content
[Naive Approach] - Process Each Query by Moving to Parent Repeatedly - O(q × k) Time and O(1) Space
For each query [u, k], we repeatedly move from the current node to its parent k times. If at any point the current node becomes -1, then the k-th ancestor does not exist. Otherwise, the node reached after k moves is the answer for that query.
#include <iostream>
#include <vector>
using namespace std;
vector<int> kthAncestorQueries(vector<int>& arr, vector<vector<int>>& queries) {
vector<int> res;
// Process each query independently
for (auto& q : queries) {
int node = q[0];
int k = q[1];
// Move to the parent k times
while (k > 0 && node != -1) {
node = arr[node];
k--;
}
res.push_back(node);
}
return res;
}
int main() {
vector<int> arr = {-1, 0, 0, 1, 1};
vector<vector<int>> queries = {
{4, 1},
{3, 2},
{4, 3}
};
vector<int> ans = kthAncestorQueries(arr, queries);
for (int x : ans) {
cout << x << " ";
}
cout << "\n";
return 0;
}
import java.util.ArrayList;
import java.util.List;
class GFG {
static ArrayList<Integer> kthAncestorQueries(int[] arr, int[][] queries) {
ArrayList<Integer> res = new ArrayList<>();
// Process each query independently
for (int[] q : queries) {
int node = q[0];
int k = q[1];
// Move to the parent k times
while (k > 0 && node!= -1) {
node = arr[node];
k--;
}
res.add(node);
}
return res;
}
public static void main(String[] args) {
int[] arr = {-1, 0, 0, 1, 1};
int[][] queries = {
{4, 1},
{3, 2},
{4, 3}
};
ArrayList<Integer> res = kthAncestorQueries(arr, queries);
for (int x : res) {
System.out.print(x + " ");
}
System.out.println();
}
}
def kthAncestorQueries(arr, queries):
res = []
# Process each query independently
for q in queries:
node = q[0]
k = q[1]
# Move to the parent k times
while k > 0 and node!= -1:
node = arr[node]
k -= 1
res.append(node)
return res
if __name__ == '__main__':
arr = [-1, 0, 0, 1, 1]
queries = [
[4, 1],
[3, 2],
[4, 3]
]
res = kthAncestorQueries(arr, queries)
for x in res:
print(x, end=' ')
print()
using System;
using System.Collections.Generic;
class GFG {
static List<int> kthAncestorQueries(int[] arr, int[][] queries) {
List<int> res = new List<int>();
// Process each query independently
foreach (int[] q in queries) {
int node = q[0];
int k = q[1];
// Move to the parent k times
while (k > 0 && node!= -1) {
node = arr[node];
k--;
}
res.Add(node);
}
return res;
}
static void Main(string[] args) {
int[] arr = { -1, 0, 0, 1, 1 };
int[][] queries = {
new int[] { 4, 1 },
new int[] { 3, 2 },
new int[] { 4, 3 }
};
List<int> res = kthAncestorQueries(arr, queries);
foreach (int x in res) {
Console.Write(x + " ");
}
Console.WriteLine();
}
}
function kthAncestorQueries(arr, queries) {
let res = [];
// Process each query independently
for (let q of queries) {
let node = q[0];
let k = q[1];
// Move to the parent k times
while (k > 0 && node!= -1) {
node = arr[node];
k--;
}
res.push(node);
}
return res;
}
// Driver code
let arr = [-1, 0, 0, 1, 1];
let queries = [
[4, 1],
[3, 2],
[4, 3]
];
let res = kthAncestorQueries(arr, queries);
for (let x of res) {
process.stdout.write(x + ' ');
}
Output
1 0 -1
[Expected Approach] - Using Binary Lifting - O(n log n + q log n) Time and O(n log n) Space
Instead of moving one level up at a time for every query, we precompute the ancestors of each node at powers of two distances. Let up[i][j] denote the 2^j-th ancestor of node i. For a query [u, k], we represent k in binary and jump upward using the precomputed ancestors corresponding to the set bits of k. This allows us to find the k-th ancestor in O(log n) time per query after an O(n log n) preprocessing step.
Precomputation
Create a binary lifting table up[i][j], where up[i][j] stores the 2^j-th ancestor of node i.
- up[i][0] is the direct parent of node i.
For j > 0, the 2^j-th ancestor of a node can be obtained by making two consecutive jumps of length 2^(j-1). Therefore, if up[i][j-1] is the 2^(j-1)-th ancestor of node i, then the 2^j-th ancestor is simply the 2^(j-1)-th ancestor of up[i][j-1]. If up[i][j-1] does not exists it remains -1.
up[i][j] = up[ up[i][j-1] ][j-1]
For example, to find the 8-th ancestor of a node, we can first jump to its 4-th ancestor and then jump another 4 levels up from there.
This allows us to precompute ancestors at distances:
1, 2, 4, 8, 16, ...
for every node.
Finding the k-th Ancestor
Represent k in binary and process its bits from least significant to most significant.
For every set bit j in k:
- Move the current node to its 2^j-th ancestor using up[node][j].
- If the node becomes -1 at any step, the k-th ancestor does not exist.
For example, if:
k = 13 = (1101)₂ = 8 + 4 + 1
then we can reach the 13-th ancestor by making the following jumps:
1-step jump -> 4-step jump -> 8-step jump
using the precomputed table. Since at most log n bits are processed, each query is answered in O(log n) time.
#include <iostream>
#include <vector>
using namespace std;
vector<int> kthAncestorQueries(vector<int>& arr, vector<vector<int>>& queries) {
int n = arr.size();
// Maximum power of 2 needed for binary lifting
int lg = 1;
while ((1 << lg) <= n) {
lg++;
}
// up[i][j] stores the 2^j-th ancestor of node i
vector<vector<int>> up(n, vector<int>(lg, -1));
// Store immediate parents
for (int i = 0; i < n; i++) {
up[i][0] = arr[i];
}
// Build binary lifting table
for (int j = 1; j < lg; j++) {
for (int i = 0; i < n; i++) {
if (up[i][j - 1] != -1) {
up[i][j] = up[up[i][j - 1]][j - 1];
}
}
}
vector<int> res;
for (auto& q : queries) {
int node = q[0];
int k = q[1];
// Jump through ancestors according to the
// set bits in the binary representation of k
for (int bit = 0; bit < lg && node != -1; bit++) {
if (k & (1 << bit)) {
node = up[node][bit];
}
}
res.push_back(node);
}
return res;
}
int main() {
vector<int> arr = {-1, 0, 0, 1, 1};
vector<vector<int>> queries = {
{4, 1},
{3, 2},
{4, 3}
};
vector<int> res = kthAncestorQueries(arr, queries);
for (int x : res) {
cout << x << " ";
}
cout << "\n";
return 0;
}
import java.util.ArrayList;
import java.util.List;
public class GFG {
public static ArrayList<Integer> kthAncestorQueries(int[] arr, int[][] queries) {
int n = arr.length;
// Maximum power of 2 needed for binary lifting
int lg = 1;
while ((1 << lg) <= n) {
lg++;
}
// up[i][j] stores the 2^j-th ancestor of node i
int[][] up = new int[n][lg];
for (int i = 0; i < n; i++) {
for (int j = 0; j < lg; j++) {
up[i][j] = -1;
}
}
// Store immediate parents
for (int i = 0; i < n; i++) {
up[i][0] = arr[i];
}
// Build binary lifting table
for (int j = 1; j < lg; j++) {
for (int i = 0; i < n; i++) {
if (up[i][j - 1]!= -1) {
up[i][j] = up[up[i][j - 1]][j - 1];
}
}
}
ArrayList<Integer> res = new ArrayList<>();
for (int[] q : queries) {
int node = q[0];
int k = q[1];
// Jump through ancestors according to the
// set bits in the binary representation of k
for (int bit = 0; bit < lg && node!= -1; bit++) {
if ((k & (1 << bit))!= 0) {
node = up[node][bit];
}
}
res.add(node);
}
return res;
}
public static void main(String[] args) {
int[] arr = {-1, 0, 0, 1, 1};
int[][] queries = {
{4, 1},
{3, 2},
{4, 3}
};
ArrayList<Integer> res = kthAncestorQueries(arr, queries);
for (int x : res) {
System.out.print(x + " ");
}
System.out.println();
}
}
def kthAncestorQueries(arr, queries):
n = len(arr)
# Maximum power of 2 needed for binary lifting
lg = 1
while (1 << lg) <= n:
lg += 1
# up[i][j] stores the 2^j-th ancestor of node i
up = [[-1 for _ in range(lg)] for _ in range(n)]
# Store immediate parents
for i in range(n):
up[i][0] = arr[i]
# Build binary lifting table
for j in range(1, lg):
for i in range(n):
if up[i][j - 1]!= -1:
up[i][j] = up[up[i][j - 1]][j - 1]
res = []
for q in queries:
node = q[0]
k = q[1]
# Jump through ancestors according to the
# set bits in the binary representation of k
for bit in range(lg):
if node!= -1 and (k & (1 << bit)):
node = up[node][bit]
res.append(node)
return res
if __name__ == '__main__':
arr = [-1, 0, 0, 1, 1]
queries = [
[4, 1],
[3, 2],
[4, 3]
]
res = kthAncestorQueries(arr, queries)
for x in res:
print(x, end=' ')
print()
using System;
using System.Collections.Generic;
public class GFG
{
public static List<int> kthAncestorQueries(int[] arr, int[][] queries)
{
int n = arr.Length;
// Maximum power of 2 needed for binary lifting
int lg = 1;
while ((1 << lg) <= n)
{
lg++;
}
// up[i][j] stores the 2^j-th ancestor of node i
int[][] up = new int[n][];
for (int i = 0; i < n; i++)
{
up[i] = new int[lg];
Array.Fill(up[i], -1);
}
// Store immediate parents
for (int i = 0; i < n; i++)
{
up[i][0] = arr[i];
}
// Build binary lifting table
for (int j = 1; j < lg; j++)
{
for (int i = 0; i < n; i++)
{
if (up[i][j - 1]!= -1)
{
up[i][j] = up[up[i][j - 1]][j - 1];
}
}
}
List<int> res = new List<int>();
foreach (int[] q in queries)
{
int node = q[0];
int k = q[1];
// Jump through ancestors according to the
// set bits in the binary representation of k
for (int bit = 0; bit < lg && node!= -1; bit++)
{
if ((k & (1 << bit))!= 0)
{
node = up[node][bit];
}
}
res.Add(node);
}
return res;
}
public static void Main()
{
int[] arr = {-1, 0, 0, 1, 1};
int[][] queries = {
new int[] {4, 1},
new int[] {3, 2},
new int[] {4, 3}
};
List<int> res = kthAncestorQueries(arr, queries);
foreach (int x in res)
{
Console.Write(x + " ");
}
Console.WriteLine();
}
}
function kthAncestorQueries(arr, queries) {
const n = arr.length;
// Maximum power of 2 needed for binary lifting
let lg = 1;
while ((1 << lg) <= n) {
lg++;
}
// up[i][j] stores the 2^j-th ancestor of node i
const up = Array.from({ length: n }, () => Array(lg).fill(-1));
// Store immediate parents
for (let i = 0; i < n; i++) {
up[i][0] = arr[i];
}
// Build binary lifting table
for (let j = 1; j < lg; j++) {
for (let i = 0; i < n; i++) {
if (up[i][j - 1]!= -1) {
up[i][j] = up[up[i][j - 1]][j - 1];
}
}
}
const res = [];
for (const q of queries) {
let node = q[0];
let k = q[1];
// Jump through ancestors according to the
// set bits in the binary representation of k
for (let bit = 0; bit < lg && node!= -1; bit++) {
if (k & (1 << bit)) {
node = up[node][bit];
}
}
res.push(node);
}
return res;
}
// Driver code
const arr = [-1, 0, 0, 1, 1];
const queries = [
[4, 1],
[3, 2],
[4, 3]
];
const res = kthAncestorQueries(arr, queries);
for (const x of res) {
process.stdout.write(x + ' ');
}
console.log();
Output
1 0 -1

