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.
- We will store the next occurrence of the element in a temporary array.
- 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
- We start with a segment arr[0 . . . n-1].
- Every time we divide the current segment into two halves if it has not yet become a segment of length 1. Then call the same procedure on both halves, and for each such segment, we store the sorted array in each segment as in merge sort.
- Also, the tree will be a Full Binary Tree because we always divide segments into two halves at every level.
- Since the constructed tree is always a full binary tree with n leaves, there will be N-1 internal nodes. So the total number of nodes will be 2*N – 1.
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
- We store the next right occurrence of every element.
- If the element has the last occurrence then we store ‘N'(Length of the 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
|
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.