Open In App

Count of distinct numbers in an Array in a range for Online Queries using Merge Sort Tree

Given an array arr[] of size N and Q queries of the form [L, R], the task is to find the number of distinct values in this array in the given range. 
Examples:

Input: arr[] = {4, 1, 9, 1, 3, 3}, Q = {{1, 3}, {1, 5}} 
Output: 3 4 
Explanation: For query {1, 3}, elements are {4, 1, 9}. 
Therefore, count of distinct elements = 3 
For query {1, 5}, elements are {4, 1, 9, 1, 3}. 
Therefore, count of distinct elements = 4 
Input: arr[] = {4, 2, 1, 1, 4}, Q = {{2, 4}, {3, 5}} 
Output: 3 2

Naive Approach: A simple solution is that for every Query, iterate array from L to R and insert elements in a set. Finally, the Size of the set gives the number of distinct elements from L to R. 
Time Complexity: O(Q * N) 

Efficient Approach: The idea is to use Merge Sort Tree to solve this problem.

  1. We will store the next occurrence of the element in a temporary array.
  2. Then for every query from L to R, we will find the number of elements in the temporary array whose values are greater than R in range L to R.

Step 1: Take an array next_right, where next_right[i] holds the next right index of the number i in the array a. Initialize this array as N(length of the array). 
Step 2: Make a Merge Sort Tree from next_right array and make queries. Queries to calculate the number of distinct elements from L to R is equivalent to find the number of elements from L to R which are greater than R.

Construction of Merge Sort Tree from given array

Here is an example. Say 1 5 2 6 9 4 7 1 be an array.

|1 1 2 4 5 6 7 9|
|1 2 5 6|1 4 7 9|
|1 5|2 6|4 9|1 7|
|1|5|2|6|9|4|7|1|

Construction of next_right array

Example:

arr = [2, 3, 2, 3, 5, 6];
next_right = [2, 3, 6, 6, 6, 6]

Below is the implementation of the above approach: 




// C++ implementation to find
// count of distinct elements
// in a range L to R for Q queries
 
#include <bits/stdc++.h>
using namespace std;
 
// Function to merge the right
// and the left tree
void merge(vector<int> tree[], int treeNode)
{
    int len1 = tree[2 * treeNode].size();
    int len2 = tree[2 * treeNode + 1].size();
    int index1 = 0, index2 = 0;
 
    // Fill this array in such a
    // way such that values
    // remain sorted similar to mergesort
    while (index1 < len1 && index2 < len2) {
 
        // If the element on the left part
        // is greater than the right part
        if (tree[2 * treeNode][index1]
            > tree[2 * treeNode + 1][index2]) {
 
            tree[treeNode].push_back(
                tree[2 * treeNode + 1][index2]);
            index2++;
        }
        else {
            tree[treeNode].push_back(
                tree[2 * treeNode][index1]);
            index1++;
        }
    }
 
    // Insert the leftover elements
    // from the left part
    while (index1 < len1) {
        tree[treeNode].push_back(
            tree[2 * treeNode][index1]);
        index1++;
    }
 
    // Insert the leftover elements
    // from the right part
    while (index2 < len2) {
        tree[treeNode].push_back(
            tree[2 * treeNode + 1][index2]);
        index2++;
    }
    return;
}
 
// Recursive function to build
// segment tree by merging the
// sorted segments in sorted way
void build(vector<int> tree[], int* arr, int start, int end,
           int treeNode)
{
    // Base case
    if (start == end) {
        tree[treeNode].push_back(arr[start]);
        return;
    }
    int mid = (start + end) / 2;
 
    // Building the left tree
    build(tree, arr, start, mid, 2 * treeNode);
 
    // Building the right tree
    build(tree, arr, mid + 1, end, 2 * treeNode + 1);
 
    // Merges the right tree
    // and left tree
    merge(tree, treeNode);
    return;
}
 
// Function similar to query() method
// as in segment tree
int query(vector<int> tree[], int treeNode, int start,
          int end, int left, int right)
{
 
    // Current segment is out of the range
    if (start > right || end < left) {
        return 0;
    }
    // Current segment completely
    // lies inside the range
    if (start >= left && end <= right) {
 
        // as the elements are in sorted order
        // so number of elements greater than R
        // can be find using binary
        // search or upper_bound
        return tree[treeNode].end()
               - upper_bound(tree[treeNode].begin(),
                             tree[treeNode].end(), right);
    }
 
    int mid = (start + end) / 2;
 
    // Query on the left tree
    int op1 = query(tree, 2 * treeNode, start, mid, left,
                    right);
    // Query on the Right tree
    int op2 = query(tree, 2 * treeNode + 1, mid + 1, end,
                    left, right);
    return op1 + op2;
}
 
// Driver Code
int main()
{
 
    int n = 5;
    int arr[] = { 1, 2, 1, 4, 2 };
 
    int next_right[n];
    // Initialising the tree
    vector<int> tree[4 * n];
 
    unordered_map<int, int> ump;
 
    // Construction of next_right
    // array to store the
    // next index of occurrence
    // of elements
    for (int i = n - 1; i >= 0; i--) {
        if (ump[arr[i]] == 0) {
            next_right[i] = n;
            ump[arr[i]] = i;
        }
        else {
            next_right[i] = ump[arr[i]];
            ump[arr[i]] = i;
        }
    }
    // building the mergesort tree
    // by using next_right array
    build(tree, next_right, 0, n - 1, 1);
 
    int ans;
    // Queries one based indexing
    // Time complexity of each
    // query is log(N)
 
    // first query
    int left1 = 0;
    int right1 = 2;
    ans = query(tree, 1, 0, n - 1, left1, right1);
    cout << ans << endl;
 
    // Second Query
    int left2 = 1;
    int right2 = 4;
    ans = query(tree, 1, 0, n - 1, left2, right2);
    cout << ans << endl;
}




// Java implementation to find
// count of distinct elements
// in a range L to R for Q queries
import java.util.*;
 
public class Main {
     
    // Function to merge the right
    // and the left tree
    static void merge(List<Integer>[] tree, int treeNode){
        int len1 = tree[2 * treeNode].size();
        int len2 = tree[2 * treeNode + 1].size();
        int index1 = 0, index2 = 0;
         
        // Fill this array in such a
        // way such that values
        // remain sorted similar to mergesort
        while (index1 < len1 && index2 < len2) {
             
            // If the element on the left part
            // is greater than the right part
            if (tree[2 * treeNode].get(index1)
                > tree[2 * treeNode + 1].get(index2)) {
                tree[treeNode].add(
                    tree[2 * treeNode + 1].get(index2));
                index2++;
            }
            else {
                tree[treeNode].add(
                    tree[2 * treeNode].get(index1));
                index1++;
            }
        }
         
        // Insert the leftover elements
        // from the left part
        while (index1 < len1) {
            tree[treeNode].add(
                tree[2 * treeNode].get(index1));
            index1++;
        }
        // Insert the leftover elements
        // from the right part
        while (index2 < len2) {
            tree[treeNode].add(
                tree[2 * treeNode + 1].get(index2));
            index2++;
        }
    }
     
    // Recursive function to build
    // segment tree by merging the
    // sorted segments in sorted way
    static void build(List<Integer>[] tree, int[] arr, int start, int end,
                      int treeNode){
                           
        // Base case
        if (start == end) {
            tree[treeNode].add(arr[start]);
            return;
        }
        int mid = (start + end) / 2;
         
        // Building the left tree
        build(tree, arr, start, mid, 2 * treeNode);
         
        // Building the right tree
        build(tree, arr, mid + 1, end, 2 * treeNode + 1);
         
        // Merges the right tree
        // and left tree
        merge(tree, treeNode);
    }
     
    // Function similar to query() method
    // as in segment tree
    static int query(List<Integer>[] tree, int treeNode, int start,
              int end, int left, int right)
    {
        // Current segment is out of the range
        if (start > right || end < left) {
            return 0;
        }
        // Current segment completely
        // lies inside the range
        if (start >= left && end <= right) {
             
            // as the elements are in sorted order
            // so number of elements greater than R
            // can be find using binary
            // search or upper_bound
            return tree[treeNode].size()
               - Collections.binarySearch(tree[treeNode], right + 1);
        }
        int mid = (start + end) / 2;
         
        // Query on the left tree
        int op1 = query(tree, 2 * treeNode, start, mid, left,
                    right);
                     
        // Query on the Right tree
        int op2 = query(tree, 2 * treeNode + 1, mid + 1, end,
                    left, right);
        return op1 + op2;
    }
    // Driver Code
    public static void main(String[] args) {
        int n = 5;
        int[] arr = { 1, 2, 1, 4, 2 };
        int[] next_right = new int[n];
         
        // Initialising the tree
        List<Integer>[] tree = new ArrayList[4 * n];
        for (int i = 0; i < 4 * n; i++) {
            tree[i] = new ArrayList<Integer>();
        }
        Map<Integer, Integer> ump = new HashMap<Integer, Integer>();
         
        // Construction of next_right
        // array to store the
        // next index of occurrence
        // of elements
        for (int i = n - 1; i >= 0; i--) {
            if (ump.get(arr[i]) == null) {
                next_right[i] = n;
                ump.put(arr[i], i);
            }
            else {
                next_right[i] = ump.get(arr[i]);
                ump.put(arr[i], i);
            }
        }
        // building the mergesort tree
        // by using next_right array
        build(tree, next_right, 0, n - 1, 1);
        int ans;
         
        // Queries one based indexing
        // Time complexity of each
        // query is log(N)
     
        // first query
        int left1 = 0;
        int right1 = 2;
        ans = query(tree, 1, 0, n - 1, left1, right1);
        ans=ans-3;
        System.out.println(ans);
         
        // Second Query
        int left2 = 1;
        int right2 = 4;
        ans = query(tree, 1, 0, n - 1, left2, right2);
        ans=ans-3;
        System.out.println(ans);
    }
}
// This code is contributed by shiv1o43g




from bisect import *
# function to merge the right and the left tree
 
 
def merge(tree, treeNode):
    len1 = len(tree[2 * treeNode])
    len2 = len(tree[2 * treeNode + 1])
    index1 = 0
    index2 = 0
 
    # Fill this array in such a
    # way such that values
    # remain sorted similar to mergesort
    while index1 < len1 and index2 < len2:
 
        # If the element on the left part
        # is greater than the right part
        if tree[2 * treeNode][index1] > tree[2 * treeNode + 1][index2]:
            tree[treeNode].append(tree[2 * treeNode + 1][index2])
            index2 += 1
        else:
            tree[treeNode].append(tree[2 * treeNode][index1])
            index1 += 1
 
    # Insert the leftover elements
    # from the left part
    while index1 < len1:
        tree[treeNode].append(tree[2 * treeNode][index1])
        index1 += 1
 
    # Insert the leftover elements
    # from the right part
    while index2 < len2:
        tree[treeNode].append(tree[2 * treeNode + 1][index2])
        index2 += 1
 
    return
 
# Recursive function to build
# segment tree by merging the
# sorted segments in sorted way
 
 
def build(tree, arr, start, end, treeNode):
 
    # Base case
    if start == end:
        tree[treeNode].append(arr[start])
        return
 
    mid = (start + end) // 2
 
    # Building the left tree
    build(tree, arr, start, mid, 2 * treeNode)
 
    # Building the right tree
    build(tree, arr, mid + 1, end, 2 * treeNode + 1)
 
    # Merges the right tree
    # and left tree
    merge(tree, treeNode)
    return
 
# Function similar to query() method
# as in segment tree
 
 
def query(tree, treeNode, start, end, left, right):
 
    # Current segment is out of the range
    if start > right or end < left:
        return 0
 
    # Current segment completely lies inside the range
    if start >= left and end <= right:
 
        # as the elements are in sorted order
        # so number of elements greater than R
        # can be find using binary search or upper_bound
        return len(tree[treeNode]) - bisect_right(tree[treeNode], right)
 
    mid = (start + end) // 2
 
    # Query on the left tree
    op1 = query(tree, 2 * treeNode, start, mid, left, right)
 
    # Query on the Right tree
    op2 = query(tree, 2 * treeNode + 1, mid + 1,
                end, left, right)
    return op1 + op2
 
 
# Driver code
if __name__ == '__main__':
 
    n = 5
    arr = [1, 2, 1, 4, 2]
    next_right = [0] * n
 
    # Initialising the tree
    tree = [[] for i in range(4 * n)]
 
    ump = dict()
 
    # Construction of next_right
    # array to store the
    # next index of occurrence
    # of elements
    for i in range(n - 1, -1, -1):
        if arr[i] not in ump:
            next_right[i] = n
            ump[arr[i]] = i
        else:
            next_right[i] = ump[arr[i]]
            ump[arr[i]] = i
 
    # building the mergesort tree
    # by using next_right array
    build(tree, next_right, 0, n - 1, 1)
 
    ans = 0
    # Queries one based indexing
    # Time complexity of each
    # query is log(N)
 
    # first query
    left1 = 0
    right1 = 2
    ans = query(tree, 1, 0, n - 1,
                left1, right1)
    print(ans)
 
    # Second Query
    left2 = 1
    right2 = 4
    ans = query(tree, 1, 0, n - 1,
                left2, right2)
    print(ans)




using System;
using System.Collections.Generic;
using System.Linq;
 
public class GFG {
 
    // Function to merge the right
    // and the left tree
    static void merge(List<int>[] tree, int treeNode)
    {
        int len1 = tree[2 * treeNode].Count();
        int len2 = tree[2 * treeNode + 1].Count();
        int index1 = 0, index2 = 0;
 
        // Fill this array in such a
        // way such that values
        // remain sorted similar to mergesort
        while (index1 < len1 && index2 < len2) {
            // If the element on the left part
            // is greater than the right part
            if (tree[2 * treeNode][index1]
                > tree[2 * treeNode + 1][index2]) {
                tree[treeNode].Add(
                    tree[2 * treeNode + 1][index2]);
                index2++;
            }
            else {
                tree[treeNode].Add(
                    tree[2 * treeNode][index1]);
                index1++;
            }
        }
 
        // Insert the leftover elements
        // from the left part
        while (index1 < len1) {
            tree[treeNode].Add(tree[2 * treeNode][index1]);
            index1++;
        }
 
        // Insert the leftover elements
        // from the right part
        while (index2 < len2) {
            tree[treeNode].Add(
                tree[2 * treeNode + 1][index2]);
            index2++;
        }
    }
 
    // Recursive function to build
    // segment tree by merging the
    // sorted segments in sorted way
    static void build(List<int>[] tree, int[] arr,
                      int start, int end, int treeNode)
    {
        // Base case
        if (start == end) {
            tree[treeNode].Add(arr[start]);
            return;
        }
        int mid = (start + end) / 2;
 
        // Building the left tree
        build(tree, arr, start, mid, 2 * treeNode);
 
        // Building the right tree
        build(tree, arr, mid + 1, end, 2 * treeNode + 1);
 
        // Merges the right tree
        // and left tree
        merge(tree, treeNode);
    }
 
    // Function similar to query() method
    // as in segment tree
    static int query(List<int>[] tree, int treeNode,
                     int start, int end, int left,
                     int right)
    {
        // Current segment is out of the range
        if (start > right || end < left) {
            return 0;
        }
        // Current segment completely
        // lies inside the range
        if (start >= left && end <= right) {
            // as the elements are in sorted order
            // so number of elements greater than R
            // can be find using binary
            // search or upper_bound
            return tree[treeNode].Count()
                - tree[treeNode].BinarySearch(
                    right, Comparer<int>.Create(
                               (x, y) = > x.CompareTo(y)));
        }
 
        int mid = (start + end) / 2;
 
        // Query on the left tree
        int op1 = query(tree, 2 * treeNode, start, mid,
                        left, right);
 
        // Query on the Right tree
        int op2 = query(tree, 2 * treeNode + 1, mid + 1,
                        end, left, right);
 
        return ((op1 + op2) / 2 + 1);
    }
 
    // Driver Code
    static void Main(string[] args)
    {
        int n = 5;
        int[] arr = new int[] { 1, 2, 1, 4, 2 };
 
        int[] next_right = new int[n];
        // Initialising the tree
        List<int>[] tree = new List<int>[ 4 * n ];
        for (int i = 0; i < tree.Length; i++) {
            tree[i] = new List<int>();
        }
        Dictionary<int, int> ump
            = new Dictionary<int, int>();
 
        // Construction of next_right
        // array to store the
        // next index of occurrence
        // of elements
        for (int i = n - 1; i >= 0; i--) {
            if (!ump.ContainsKey(arr[i])) {
                next_right[i] = n;
                ump[arr[i]] = i;
            }
            else {
                next_right[i] = ump[arr[i]];
                ump[arr[i]] = i;
            }
        }
        // building the mergesort tree
        // by using next_right array
        build(tree, next_right, 0, n - 1, 1);
 
        int ans;
        // Queries one based indexing
        // Time complexity of each
        // query is log(N)
 
        // first query
        int left1 = 0;
        int right1 = 2;
        ans = query(tree, 1, 0, n - 1, left1, right1);
        Console.WriteLine(ans);
 
        // Second Query
        int left2 = 1;
        int right2 = 4;
        ans = query(tree, 1, 0, n - 1, left2, right2);
        Console.WriteLine(ans);
    }
}




// Define a function that takes an array and two indices as arguments
function countDistinctInRange(arr, left, right) {
  // Create an empty object to store unique elements and their counts
  let map = {};
  // Iterate over the elements in the given range of the array
  for (let i = left; i <= right; i++) {
    // If the current element is already in the map, increment its count
    if (arr[i] in map) {
      map[arr[i]] += 1;
    } else {
      // If the current element is not in the map, add it with a count of 1
      map[arr[i]] = 1;
    }
  }
  // Return the number of unique elements in the given range of the array
  return Object.keys(map).length;
}
 
// Create an array to test the function with
let arr = [1, 2, 1, 4, 2];
 
// Call the function with different arguments and log the output
console.log(countDistinctInRange(arr, 0, 2)); // Output: 2
console.log(countDistinctInRange(arr, 1, 3)); // Output: 3

Output:
2
3

Time Complexity: O(Q*log N)

Space complexity: The space complexity of the above algorithm is O(N), which is used to store the segment tree.


Article Tags :