Ancestor Queries on a Tree

Last Updated : 22 Jul, 2026

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.

[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.

C++
#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;
}
Java
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();
  }
}
Python
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()
C#
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();
    }
}
JavaScript
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.

C++
#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;
}
Java
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();
    }
}
Python
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()
C#
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();
    }
}
JavaScript
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 
Comment