# Explain an alternative Sorting approach for MO’s Algorithm

• Last Updated : 08 Mar, 2023

MO’s Algorithm is an algorithm designed to efficiently answer range queries in an array in linear time. It is a divide-and-conquer approach that involves pre-processing the array, partitioning it into blocks, and then solving the queries in each of the blocks.

### Alternate Approach for Sorting:

An alternate approach that has been proposed is known as the double-ended priority queue (DEQ) approach. This approach uses a double-ended priority queue (DEQ) to store the elements of the array. The elements are then sorted using the DEQ, which allows for faster retrieval of elements.

### Double-Ended Priority Queue (DEQ):

A double-ended priority queue (DEQ) is a data structure that stores items in an order of priority and allows access from both the front and back of the queue.

The DEQ allows for both the insertion and deletion of elements in O(log n) time, where n is the number of elements in the DEQ. This makes it an efficient data structure for maintaining the order of elements in a range as it allows for fast insertion and deletion of elements.

Consider an array that consists of N elements and there are Q queries where each query consists of two indices l and r denoting the range of the query.

Steps involved in the implementation of code:

• The block size is calculated as the square root of N which will be used to partition the queries.
• Then, the queries are sorted according to their left indices and whether the block size is even or odd.
• A sliding window is created that stores the range of indices from l to r
• For each query, the elements in the window are updated by adding or removing elements from the range l to r.
• The variable cnt[a[x]] is used to count the number of times an element x appears in the window.
• The variable ans[0] is used to keep track of the number of distinct elements in the window.
• The answer to each query is stored in the array ans[].

Below is the implementation of Double ended Priority Queue (DEQ):

## C++

 // C++ code to implement the approaach #include using namespace std; // N is the maximum number of elements// in the arrayconst int N = 1e5 + 5;int n, q, a[N], ans[N], cnt[N], block_size; // Query structure for storing query detailsstruct query {    int l, r, id;} qry[N]; // Function to compare two queriesbool cmp(query a, query b){    // If the query is in different blocks,    // sort according to lth index    if (a.l / block_size != b.l / block_size)        return a.l < b.l;     // If the queries are in the same block,    // sort according to the parity of the    // block number    return (a.l / block_size & 1) ? a.r < b.r : a.r > b.r;} // Function to add an element to the// current rangevoid add(int x){    cnt[a[x]]++;     // Increase the answer if the element    // is added for the first time in    // this range    if (cnt[a[x]] == 1)        ans[0]++;} // Function to remove an element from// the current rangevoid remove(int x){    cnt[a[x]]--;     // Decrease the answer if the element    // is completely removed from    // this range    if (cnt[a[x]] == 0)        ans[0]--;} // Driver codeint main(){    n = 12;    q = 3;    block_size = sqrt(n);     // Example array    int array[] = { 1, 2, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6 };     // Store the elements in the array    for (int i = 0; i < n; i++)        a[i + 1] = array[i];     // Example range queries    int l1 = 1, r1 = 4, l2 = 2, r2 = 6, l3 = 3, r3 = 8;     // Store the query details    qry[0].l = l1;    qry[0].r = r1;    qry[0].id = 0;    qry[1].l = l2;    qry[1].r = r2;    qry[1].id = 1;    qry[2].l = l3;    qry[2].r = r3;    qry[2].id = 2;     // Sort the queries according to their    // lth and rth indices    sort(qry, qry + q, cmp);     // l and r denote the current range    int l = 1, r = 0;     // Iterate through all the queries    for (int i = 0; i < q; i++) {         // Expand the range to the left        while (l > qry[i].l)            add(--l);         // Expand the range to the right        while (r < qry[i].r)            add(++r);         // Shrink the range from the left        while (l < qry[i].l)            remove(l++);         // Shrink the range from the right        while (r > qry[i].r)            remove(r--);         // Store the answer for the        // current query        ans[qry[i].id] = ans[0];    }     // Print answers to the queries    for (int i = 0; i < q; i++)        cout << ans[i] << endl;     return 0;}

## Java

 // Java code to implement the approaachimport java.util.*; public class Main {    static class Query {        int l, r, id;         // Query structure for storing query details        Query(int l, int r, int id) {            this.l = l;            this.r = r;            this.id = id;        }    }     static int n, q, block_size;    static int[] a, ans, cnt;     // Function to compare two queries    static class QueryComparator implements Comparator {        @Override        public int compare(Query a, Query b) {            // If the query is in different blocks,            // sort according to lth index            if (a.l / block_size != b.l / block_size) {                return Integer.compare(a.l, b.l);            }            // If the queries are in the same block,            // sort according to the parity of the            // block number            return ((a.l / block_size) & 1) == 0 ? Integer.compare(a.r, b.r) : Integer.compare(b.r, a.r);        }    }     // Function to add an element to the    // current range    static void add(int x) {        cnt[a[x]]++;                 // Increase the answer if the element        // is added for the first time in        // this range        if (cnt[a[x]] == 1) {            ans[0]++;        }    }     // Function to remove an element from    // the current range    static void remove(int x) {        cnt[a[x]]--;                 // Decrease the answer if the element        // is completely removed from        // this range        if (cnt[a[x]] == 0) {            ans[0]--;        }    }     // Driver code    public static void main(String[] args) {        n = 12;        q = 3;        block_size = (int) Math.sqrt(n);         // Example array        int[] array = {1, 2, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6};         // Store the elements in the array        a = new int[n + 1];        for (int i = 0; i < n; i++) {            a[i + 1] = array[i];        }         // Example range queries        int l1 = 1, r1 = 4, l2 = 2, r2 = 6, l3 = 3, r3 = 8;         // Store the query details        Query[] qry = new Query[q];        qry[0] = new Query(l1, r1, 0);        qry[1] = new Query(l2, r2, 1);        qry[2] = new Query(l3, r3, 2);         // Sort the queries according to their        // lth and rth indices        Arrays.sort(qry, new QueryComparator());         // l and r denote the current range        int l = 1, r = 0;        cnt = new int[n];        ans = new int[q + 1];         // Iterate through all the queries        for (int i = 0; i < q; i++) {            // Expand the range to the left            while (l > qry[i].l)                add(--l);             // Expand the range to the right            while (r < qry[i].r)                add(++r);             // Shrink the range from the left            while (l < qry[i].l)                remove(l++);             // Shrink the range from the right            while (r > qry[i].r)                remove(r--);             // Store the answer for the            // current query           ans[qry[i].id] = ans[0];        }                 // Print answers to the queries        for (int i = 0; i < q; i++)            System.out.println(ans[i]);    }} // This code is contributed by Pushpesh Raj.

## Python3

 # Python code to implement the approaach import math # N is the maximum number of elements# in the arrayN = int(1e5) + 5n, q, block_size = 0, 0, 0a = [0] * Nans = [0] * Ncnt = [0] * N # Query structure for storing query detailsclass Query:    def __init__(self, l, r, id):        self.l = l        self.r = r        self.id = id # Function to compare two queriesdef cmp(a, b):    # If the query is in different blocks,    # sort according to lth index    if a.l // block_size != b.l // block_size:        return a.l < b.l     # If the queries are in the same block,    # sort according to the parity of the    # block number    return a.r < b.r if a.l // block_size & 1 else a.r > b.r # Function to add an element to the# current rangedef add(x):    cnt[a[x]] += 1     # Increase the answer if the element    # is added for the first time in    # this range    if cnt[a[x]] == 1:        ans[0] += 1 # Function to remove an element from# the current rangedef remove(x):    cnt[a[x]] -= 1     # Decrease the answer if the element    # is completely removed from    # this range    if cnt[a[x]] == 0:        ans[0] -= 1 # Driver codeif __name__ == '__main__':    n = 12    q = 3    block_size = int(math.sqrt(n))     # Example array    array = [1, 2, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6]     # Store the elements in the array    for i in range(n):        a[i + 1] = array[i]     # Example range queries    l1, r1, l2, r2, l3, r3 = 1, 4, 2, 6, 3, 8     # Store the query details    qry = [Query(l1, r1, 0), Query(l2, r2, 1), Query(l3, r3, 2)]     # Sort the queries according to their    # lth and rth indices    qry.sort(key=lambda x: cmp(x, x))     # l and r denote the current range    l, r = 1, 0     # Iterate through all the queries    for i in range(q):        # Expand the range to the left        while l > qry[i].l:            add(l-1)            l -= 1         # Expand the range to the right        while r < qry[i].r:            add(r+1)            r += 1         # Shrink the range from the left        while l < qry[i].l:            remove(l)            l += 1         # Shrink the range from the right        while r > qry[i].r:            remove(r)            r -= 1         # Store the answer for the        # current query        ans[qry[i].id] = ans[0]     # Print answers to the queries    for i in range(q):        print(ans[i])

## C#

 using System; public class Program{  // N is the maximum number of elements  // in the array  const int N = 100005;  static int n, q, block_size;  static int[] a = new int[N], ans = new int[N], cnt = new int[N];  // Query structure for storing query details  struct Query  {    public int l, r, id;     public Query(int l, int r, int id)    {      this.l = l;      this.r = r;      this.id = id;    }  }   // Function to compare two queries  static int cmp(Query a, Query b)  {    // If the query is in different blocks,    // sort according to lth index    if (a.l / block_size != b.l / block_size)    {      return a.l / block_size - b.l / block_size;    }    // If the queries are in the same block,    // sort according to the parity of the    // block number    return (a.l / block_size & 1) != 0 ? a.r - b.r : b.r - a.r;  }  // Function to add an element to the  // current range  static void add(int x)  {    cnt[a[x]]++;    // Increase the answer if the element    // is added for the first time in    // this range    if (cnt[a[x]] == 1)    {      ans[0]++;    }  }  // Function to remove an element from  // the current range  static void remove(int x)  {    // Decrease the answer if the element    // is completely removed from    // this range    cnt[a[x]]--;     if (cnt[a[x]] == 0)    {      ans[0]--;    }  }  // Driver code  public static void Main()  {    n = 12;    q = 3;    block_size = (int)Math.Sqrt(n);     int[] array = { 1, 2, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6 };     // Store the elements in the array    for (int i = 0; i < n; i++)    {      a[i + 1] = array[i];    }    // Example range queries    Query[] qry = {      new Query(1, 4, 0),      new Query(2, 6, 1),      new Query(3, 8, 2)      };     // Sort the queries according to their    // lth and rth indices    Array.Sort(qry, cmp);    // l and r denote the current range    int l = 1, r = 0;    // Iterate through all the queries    for (int i = 0; i < q; i++)    {      // Expand the range to the left      while (l > qry[i].l)      {        add(--l);      }      // Expand the range to the right      while (r < qry[i].r)      {        add(++r);      }      // Shrink the range from the left      while (l < qry[i].l)      {        remove(l++);      }      // Shrink the range from the right      while (r > qry[i].r)      {        remove(r--);      }      // Store the answer for the      // current query      ans[qry[i].id] = ans[0];    }    //Print answer    for (int i = 0; i < q; i++)    {      Console.WriteLine(ans[i]);    }  }}

## Javascript

 // Javascript code to implement the approaach // N is the maximum number of elements// in the arrayconst N = 1e5 + 5;let n = 0, q = 0, block_size = 0;const a = new Array(N).fill(0);const ans = new Array(N).fill(0);const cnt = new Array(N).fill(0); // Query structure for storing query detailsclass Query {  constructor(l, r, id) {    this.l = l;    this.r = r;    this.id = id;  }}  // Function to compare two queriesfunction cmp(a, b) {    // If the query is in different blocks,    // sort according to lth index  if (Math.floor(a.l / block_size) !== Math.floor(b.l / block_size)) {    return a.l < b.l;  }    // If the queries are in the same block,    // sort according to the parity of the    // block number  return a.r < b.r ? (a.l % 2 === 0 ? 1 : -1) * (a.r - b.r) : (a.l % 2 === 0 ? 1 : -1) * (b.r - a.r);}  // Function to add an element to the// current rangefunction add(x) {       // Increase the answer if the element  // is added for the first time in  // this range  cnt[a[x]]++;  if (cnt[a[x]] === 1) {    ans[0]++;  }}  // Function to remove an element from// the current rangefunction remove(x) {  cnt[a[x]]--;  // Decrease the answer if the element  // is completely removed from  // this range  if (cnt[a[x]] === 0) {    ans[0]--;  }}  // Driver coden = 12;q = 3;block_size = Math.floor(Math.sqrt(n)); // Example arrayconst array = [1, 2, 2, 2, 3, 4, 5, 5, 5, 6, 6, 6]; // Store the elements in the arrayfor (let i = 0; i < n; i++) {  a[i + 1] = array[i];} // Example range queriesconst l1 = 1, r1 = 4, l2 = 2, r2 = 6, l3 = 3, r3 = 8;// Store the query detailsconst qry = [new Query(l1, r1, 0), new Query(l2, r2, 1), new Query(l3, r3, 2)]; // Sort the queries according to their// lth and rth indicesqry.sort(cmp);  // l and r denote the current rangelet l = 1, r = 0; // Iterate through all the queriesfor (let i = 0; i < q; i++) {       // Expand the range to the left  while (l > qry[i].l) {           add(l - 1);    l--;  }     // Expand the range to the right  while (r < qry[i].r) {    add(r + 1);    r++;  }     // Shrink the range from the left  while (l < qry[i].l) {    remove(l);    l++;  }     // Shrink the range from the right  while (r > qry[i].r) {    remove(r);    r--;  }     // Store the answer for the  // current query  ans[qry[i].id] = ans[0];} // Print answers to the queriesfor (let i = 0; i < q; i++) {  console.log(ans[i]);} // This code is contributed by sdeadityasharma

Output

4
3
4

Time Complexity: O(Q * sqrt(N) + N*log N + Q*logQ)
Auxiliary Space: O(N)

### Advantages of the Double-Ended Priority Queue Approach:

• Faster Access Time: The double-ended priority queue approach allows for faster access times than other data structures because it can be accessed from both the front and the back of the queue.
• More Flexibility: Offers more flexibility than other data structures because the data structure allows for both enqueue and dequeue operations, which are typically costly operations. Additionally, the double-ended priority queue approach also allows for the insertion and deletion of elements at any point in the queue.
• Ease of Use: This approach is relatively straightforward to use and understand. This makes it a good choice for applications that require quick access to data.
• Improved Performance: It offers improved performance due to the ability to efficiently search in both directions.
• Flexible Design: This is a flexible design that can be adapted to different needs. It is possible to easily adjust the priority levels for different items in the queue, allowing for greater customization and flexibility.

### Disadvantages of the Double-Ended Priority Queue Approach:

• More Complex: The double-ended priority queue approach is more complex than other data structures and requires more time and effort to implement the data structure correctly.
• Limited Capacity: The double-ended priority queue approach has limited capacity, meaning it can only hold a certain amount of data. This can be a problem if the queue needs to store large amounts of data, as it will not be able to accommodate it.
• More Memory: This approach also requires more memory than other data structures because it needs to store both enqueue and dequeue information.

### Applications of the Double-Ended Priority Queue Approach:

• Scheduling: Often used in scheduling applications because the data structure allows for quick access to data and is relatively easy to implement and can also be used to store and access scheduling information, such as deadlines and appointments.
• Sorting: Also often used in sorting applications because the data structure allows for quick access to data and can also be used to store and access sorted data, such as lists of names or numbers.
• Data Analysis: This approach is also often used in data analysis applications. This approach can also be used to store and access large amounts of data, such as financial records or customer data.
• Task Management: The double-ended priority queue approach can be used for task management. It can be used to store tasks in a priority order, making it easier to prioritize them and ensure the most important tasks are completed first.
• Job Queuing: The double-ended priority queue approach can be used for job queuing to store jobs in priority order, allowing for efficient retrieval of the most important jobs.

### Conclusion:

The double-ended priority queue (DEQ) approach is an alternate sorting approach for MO’s algorithm. It has several advantages over the block sorting approach, such as being able to sort elements in both ascending and descending order, being more efficient, and requiring less storage space. However, it also has some drawbacks, such as requiring extra memory, being limited to a certain number of elements, and not being as flexible as block sorting. Despite these drawbacks, it may still be useful in certain applications due to its advantages.

Related Articles:

My Personal Notes arrow_drop_up