Query-Based Array Transformation
Last Updated :
17 Dec, 2023
Given an array arr[] of size N, initially, all the elements of arr are 0, you have to process q queries of the form “L R X” denoting that fill the array from index l to index r with the number x, the task is to print the final array after all the operations have been performed.
Examples:
Input: N = 4, queries = [[1, 3, 2], [2, 4, 6], [2, 3, 7]]
Output: [2, 7, 7, 6]
Explanation: Starting with an array of [0, 0 0 0] Starting with an array of [0, 0 0 0] lets perform some operations:
- Query [1, 3, 2]:
Add 2 to the elements, in the range from index 1 to index 3.
The updated array becomes [2, 2, 2, 0].
- Query [2, 4, 6]:
Add 6 to the elements in the range, from index 2 to index 4.
The updated array becomes [2, 6, 6, 6].
- Query [2, 3, 7]:
Adding 7 to the elements in the range from index 2 to index 3.
The updated array becomes [2, 7, 7, 6].
Input: N = 4, queries = [[1, 4, 2], [2, 3, 7], [1, 2, 3]]
Output: [3, 3, 7, 2]
Explanation: Given an array, with four elements initially set to zero, we will perform a series of operations on it. Let’s go through each query step by step:
- Query [1, 4 2]:
- We add 2 to all the elements in the range from index 1 to index 4. As a result, the updated array becomes [2, 2, 2, 2].
- Query [2, 3 7]:
- Next, we add 7 to all the elements in the range from index 2 to index 3. This leads to an updated array of [2, 7, 7, 2].
- Query [1, 2, 3]:
- Finally we add 3 to all the elements in the range from index 1 to index 2. This modifies the array again. Results in [3, 3, 7, 2].
So after performing all these queries on our array of zeros and making adjustments at each step according to the given ranges and values specified within each query instruction set (from adding values like ‘two’ or ‘seven’, within specific ranges) we end up with a final updated array of [3, 3, 7, 2].
Naive Approach: The basic way to solve the problem is as follows:
The given approach uses a technique to handle a sequence of queries that involve making changes, to elements in an array. It keeps track of an array called “arr” which is initially filled with zeros and then proceeds to go through a list of queries. Each query in the format [L, R, X] guides the code to update a range [L, R], in the “arr” by assigning it the value X. The algorithm carries out these updates for each query resulting in the arr“.
Follow the steps to solve the problem:
- Start by creating an array called “arr” with a size of N and initialize all its elements to zero.
- Go through each query in the “queries” list.
- For each query [L, R, X];
- Iterate through the indices from L 1 to R 1 in the “arr” array.
- Set each element within this range to the value of X.
- Once all queries have been processed, return the modified “arr” array.
Illustrations:
Lets consider an array, with four elements initially set to zero we will perform a series of operations on it. Lets go through each query step by step:
N = 4, queries = [[1, 4, 2], [2, 3, 7], [1, 2, 3]]
- Query [1, 4, 2]:
- We add 2 to all the elements in the range from index 1 to index 4. As a result the updated array becomes [2, 2, 2, 2].
- Query [2, 3, 7]:
- Next we add 7 to all the elements in the range from index 2 to index 3. This leads to an updated array of [2, 7, 7, 2].
- Query [1, 2, 3]:
- Finally we add 3 to all the elements in the range from index 1 to index 2. This modifies the array again. Results in [3, 3, 7, 2].
- So after performing all these queries on our array of zeros and making adjustments at each step according to the given ranges and values specified within each query instruction set (from adding values like ‘two’ or ‘seven’, within specific ranges) we end up with a final updated array of [3, 3, 7, 2].
Here is the implementation of the above approach:
C++
#include <iostream>
#include <vector>
using namespace std;
vector< int > DoQueries( int N, vector<vector< int > >& queries)
{
vector< int > arr(N, 0);
for ( const vector< int >& query : queries) {
int L = query[0];
int R = query[1];
int X = query[2];
for ( int i = L - 1; i < R; ++i) {
arr[i] = X;
}
}
return arr;
}
int main()
{
int N1 = 4;
vector<vector< int > > queries1
= { { 1, 4, 2 }, { 2, 3, 7 }, { 1, 2, 3 } };
vector< int > result1 = DoQueries(N1, queries1);
for ( int value : result1) {
cout << value << " " ;
}
cout << endl;
int N2 = 4;
vector<vector< int > > queries2
= { { 1, 3, 2 }, { 2, 4, 6 }, { 2, 3, 7 } };
vector< int > result2 = DoQueries(N2, queries2);
for ( int value : result2) {
cout << value << " " ;
}
cout << endl;
return 0;
}
|
Java
import java.util.*;
class GFG {
public static List<Integer>
doQueries( int N, List<List<Integer> > queries)
{
List<Integer> arr = new ArrayList<>(N);
for ( int i = 0 ; i < N; i++) {
arr.add( 0 );
}
for (List<Integer> query : queries) {
int L = query.get( 0 );
int R = query.get( 1 );
int X = query.get( 2 );
for ( int i = L - 1 ; i < R; i++) {
arr.set(i, X);
}
}
return arr;
}
public static void main(String[] args)
{
int N1 = 4 ;
List<List<Integer> > queries1
= List.of(List.of( 1 , 4 , 2 ), List.of( 2 , 3 , 7 ),
List.of( 1 , 2 , 3 ));
List<Integer> result1 = doQueries(N1, queries1);
for ( int value : result1) {
System.out.print(value + " " );
}
System.out.println();
int N2 = 4 ;
List<List<Integer> > queries2
= List.of(List.of( 1 , 3 , 2 ), List.of( 2 , 4 , 6 ),
List.of( 2 , 3 , 7 ));
List<Integer> result2 = doQueries(N2, queries2);
for ( int value : result2) {
System.out.print(value + " " );
}
System.out.println();
}
}
|
Python3
def do_queries(N, queries):
arr = [ 0 ] * N
for query in queries:
L, R, X = query
for i in range (L - 1 , R):
arr[i] = X
return arr
if __name__ = = "__main__" :
N1 = 4
queries1 = [
[ 1 , 4 , 2 ],
[ 2 , 3 , 7 ],
[ 1 , 2 , 3 ]
]
result1 = do_queries(N1, queries1)
for value in result1:
print (value, end = " " )
print ()
N2 = 4
queries2 = [
[ 1 , 3 , 2 ],
[ 2 , 4 , 6 ],
[ 2 , 3 , 7 ]
]
result2 = do_queries(N2, queries2)
for value in result2:
print (value, end = " " )
print ()
|
C#
using System;
using System.Collections.Generic;
class GFG
{
public static List< int > DoQueries( int N, List<List< int >> queries)
{
List< int > arr = new List< int >(N);
for ( int i = 0; i < N; i++)
arr.Add(0);
foreach (List< int > query in queries)
{
int L = query[0];
int R = query[1];
int X = query[2];
for ( int i = L - 1; i < R; i++)
arr[i] = X;
}
return arr;
}
public static void Main( string [] args)
{
int N1 = 4;
List<List< int >> queries1 = new List<List< int >>
{
new List< int > { 1, 4, 2 },
new List< int > { 2, 3, 7 },
new List< int > { 1, 2, 3 }
};
List< int > result1 = DoQueries(N1, queries1);
foreach ( int value in result1)
Console.Write(value + " " );
Console.WriteLine();
int N2 = 4;
List<List< int >> queries2 = new List<List< int >>
{
new List< int > { 1, 3, 2 },
new List< int > { 2, 4, 6 },
new List< int > { 2, 3, 7 }
};
List< int > result2 = DoQueries(N2, queries2);
foreach ( int value in result2)
Console.Write(value + " " );
Console.WriteLine();
}
}
|
Javascript
function doQueries(N, queries) {
const arr = new Array(N).fill(0);
for (const query of queries) {
const L = query[0];
const R = query[1];
const X = query[2];
for (let i = L - 1; i < R; i++) {
arr[i] = X;
}
}
return arr;
}
const N1 = 4;
const queries1 = [
[1, 4, 2],
[2, 3, 7],
[1, 2, 3]
];
const result1 = doQueries(N1, queries1);
console.log(result1.join( " " ));
const N2 = 4;
const queries2 = [
[1, 3, 2],
[2, 4, 6],
[2, 3, 7]
];
const result2 = doQueries(N2, queries2);
console.log(result2.join( " " ));
|
Time Complexity Analysis:
- The time complexity, for initializing the “arr” array is O(N).
- Processing each query takes O(R. L + 1) time, where R and L represent the right and left indices of the query range.
- In the worst-case scenario, each query could potentially cover the array. Hence processing all queries would take a total time complexity of
O(q*N) where q represents the number of queries.
- Overall considering both initialization and processing time complexities we can simplify it to O(q*N) where N represents the size of the array and q is the number of queries.
Space Complexity Analysis:
- The size of the “arr” array which takes up O(N) space.
- The memory required for storing all queries in a list contributes to a space complexity of O(q) where q represents the number of queries.
- Therefore when considering both factors we can conclude that overall space complexity is O(N + q).To summarize the code has a time complexity of O(q * N) and a space complexity of O(N + q) where N represents the array size and q represents the number of queries.
Efficient Approach: To solve the problem follow the below idea:
- Insert numbers from 1 to N in the set which denote indices.
- Process queries in a reverse manner and for each query find the iterators in the set for elements greater than or equal to L and elements smaller than equal to R then you update the range between the two iterators we got and then after updating we delete the values from itr1 to itr2 from set this way each index is traverse only once in all queries.
Steps to implement above Idea:
- Fill the indices” with numbers ranging from 1 to N representing the indices that are currently available.
- Next we’ll go through the queries in reverse order. This way we ensure that any changes made by queries are the final changes.
- For each query retrieve and note down three things; the left bound (L), right bound (R) and value (X). These values will be crucial for updating our array. Additionally locate iterators Itr1,and Itr2 within the indices.”
- Update the range between itr1 and itr2 in our array using X as their value. Afterward remove these processed indices (from itr1, to itr2) from our indices.”
- Once all queries have been processed successfully our array “arr” will contain the result.
Below is the implementation in C++:
C++
#include <iostream>
#include <set>
#include <vector>
using namespace std;
vector< int > DoQueries( int N, vector<vector< int > >& queries)
{
vector< int > arr(N, 0);
set< int > indices;
for ( int i = 1; i <= N; ++i) {
indices.insert(i);
}
for ( int q = queries.size() - 1; q >= 0; --q) {
int L = queries[q][0];
int R = queries[q][1];
int X = queries[q][2];
auto itr1 = indices.lower_bound(L);
auto itr2 = indices.upper_bound(R);
for ( auto it = itr1; it != itr2; ++it) {
arr[*it - 1] = X;
}
indices.erase(itr1, itr2);
}
return arr;
}
int main()
{
int N1 = 4;
vector<vector< int > > queries1
= { { 1, 4, 2 }, { 2, 3, 7 }, { 1, 2, 3 } };
vector< int > result1 = DoQueries(N1, queries1);
for ( int value : result1) {
cout << value << " ";
}
cout << endl;
int N2 = 4;
vector<vector< int > > queries2
= { { 1, 3, 2 }, { 2, 4, 6 }, { 2, 3, 7 } };
vector< int > result2 = DoQueries(N2, queries2);
for ( int value : result2) {
cout << value << " ";
}
cout << endl;
return 0;
}
|
Java
import java.util.*;
public class Queries {
public static int [] doQueries( int N, int [][] queries) {
int [] arr = new int [N];
Set<Integer> indices = new HashSet<>();
for ( int i = 1 ; i <= N; ++i) {
indices.add(i);
}
for ( int q = queries.length - 1 ; q >= 0 ; --q) {
int L = queries[q][ 0 ];
int R = queries[q][ 1 ];
int X = queries[q][ 2 ];
for ( int it = L; it <= R; ++it) {
if (indices.contains(it)) {
arr[it - 1 ] = X;
indices.remove(it);
}
}
}
return arr;
}
public static void main(String[] args) {
int N1 = 4 ;
int [][] queries1 = { { 1 , 4 , 2 }, { 2 , 3 , 7 }, { 1 , 2 , 3 } };
int [] result1 = doQueries(N1, queries1);
System.out.println(Arrays.toString(result1));
int N2 = 4 ;
int [][] queries2 = { { 1 , 3 , 2 }, { 2 , 4 , 6 }, { 2 , 3 , 7 } };
int [] result2 = doQueries(N2, queries2);
System.out.println(Arrays.toString(result2));
}
}
|
Python3
def do_queries(N, queries):
arr = [ 0 ] * N
indices = set ( range ( 1 , N + 1 ))
for q in reversed (queries):
L, R, X = q
for it in range (L, R + 1 ):
if it in indices:
arr[it - 1 ] = X
indices.remove(it)
return arr
if __name__ = = "__main__" :
N1 = 4
queries1 = [[ 1 , 4 , 2 ], [ 2 , 3 , 7 ], [ 1 , 2 , 3 ]]
result1 = do_queries(N1, queries1)
print ( * result1)
N2 = 4
queries2 = [[ 1 , 3 , 2 ], [ 2 , 4 , 6 ], [ 2 , 3 , 7 ]]
result2 = do_queries(N2, queries2)
print ( * result2)
|
C#
using System;
using System.Collections.Generic;
class Program
{
static List< int > DoQueries( int N, List<List< int >> queries)
{
List< int > arr = new List< int >( new int [N]);
SortedSet< int > indices = new SortedSet< int >();
for ( int i = 1; i <= N; ++i)
{
indices.Add(i);
}
for ( int q = queries.Count - 1; q >= 0; --q)
{
int L = queries[q][0];
int R = queries[q][1];
int X = queries[q][2];
var itr1 = indices.GetViewBetween(L, R + 1).GetEnumerator();
while (itr1.MoveNext())
{
arr[itr1.Current - 1] = X;
}
indices.RemoveWhere(x => x >= L && x <= R);
}
return arr;
}
static void Main( string [] args)
{
int N1 = 4;
List<List< int >> queries1 = new List<List< int >>
{
new List< int > { 1, 4, 2 },
new List< int > { 2, 3, 7 },
new List< int > { 1, 2, 3 }
};
List< int > result1 = DoQueries(N1, queries1);
foreach ( int value in result1)
{
Console.Write(value + " " );
}
Console.WriteLine();
int N2 = 4;
List<List< int >> queries2 = new List<List< int >>
{
new List< int > { 1, 3, 2 },
new List< int > { 2, 4, 6 },
new List< int > { 2, 3, 7 }
};
List< int > result2 = DoQueries(N2, queries2);
foreach ( int value in result2)
{
Console.Write(value + " " );
}
Console.WriteLine();
}
}
|
Javascript
function doQueries(N, queries) {
let arr = new Array(N).fill(0);
let indices = new Set();
for (let i = 1; i <= N; ++i) {
indices.add(i);
}
for (let q = queries.length - 1; q >= 0; --q) {
let L = queries[q][0];
let R = queries[q][1];
let X = queries[q][2];
for (let it = L; it <= R; ++it) {
if (indices.has(it)) {
arr[it - 1] = X;
indices. delete (it);
}
}
}
return arr;
}
let N1 = 4;
let queries1 = [[1, 4, 2], [2, 3, 7], [1, 2, 3]];
let result1 = doQueries(N1, queries1);
console.log(result1);
let N2 = 4;
let queries2 = [[1, 3, 2], [2, 4, 6], [2, 3, 7]];
let result2 = doQueries(N2, queries2);
console.log(result2);
|
Time Complexity: O(QlogN + Q), with Q representing the number of queries and N representing the length of the arr array.
Auxiliary Space: O(N), where N is number of length of the array.
Share your thoughts in the comments
Please Login to comment...