Search code examples
javaarraysalgorithmsortinghashmap

Obtain the Total number of element in the Sorted Array that are within the given Range in O(1) time


I'm trying to write a class named Range, that takes an array of integers (unsorted) of length n, containing only numbers in the range are from 0 to k.

At first, I declare a constructor which will preprocess the array via Counting Sort algorithm.

Then I want to write query() method that takes two integer arguments: a and b, which form a range of numbers from a to b and returns the total frequency of all the elements in the array having the values within the given range.

My code:

import java.util.Arrays;
import java.util.HashMap;

public class Range {

    private int[] a;
    private int k;

    public Range(int[] a, int k) {
        int index = 0;
        int[] counterArray = new int[k + 1];

        for (int i : a)
            counterArray[i]++;  // initialize counterArray

        for (int i = 0; i < counterArray.length; i++)
            while (0 < counterArray[i]) {
                a[index++] = i;
                counterArray[i]--;
            }   // end while()

        this.a = a;
        this.k = k;
    }   // end constructor()

    public int query(int a, int b) {
        HashMap<Integer, Integer> map = new HashMap<>(a);

    }   // end query()

    @Override
    public String toString() {
        return Arrays.toString(a);
    }   // end toString()
}

I chose HashMap data structure because I need query() method to be executed in constant time O(1).

So my question is: Is it possible to implement the method query() via HashMap?

If not, what are the alternatives? (Note: the time complexity should be O(1) for query(), not bothering about the space complexity).

Code in the main() :

int[] a = {13,12,13,1,2,0,0,1,3,4};
Range range = new Range(a, 13);
System.out.print(range); // prints [0,0,1,1,2,3,4,12,13,13] because array has been sorted

System.out.print(range.query(1, 4)); // calculating number of elements in the range [1, 4]

Expected Output:

5   // elements 1,1,2,3,4 are within the range [1, 4]

Explanation: provided arguments of the query() are: a=1 and b=4, hence, values to be tested are 1,2,3,4. The output should be 5 because there are 5 elements: 1,1,2,3,4.


Solution

  • To obtain the number of elements in the given range (from a to b inclusive) in O(1) time after the array has been sorted, you don't need to use HashMap. Instead, you can reuse the countingArray by making it an instance variable.

    This approach also requires a slight modification of the sorting in order to retain the values in the countingArray intact. It's done by introducing one additional variable.

    Note that it's a good practice to avoid mutating the input, that why in the code I've used Arrays.copyOf() (you can remove it, if you consider it irrelevant for this exercise).

    I've extracted the logic responsible for sorting from the constructor into a separate method. And introduced a method which is meant to calculate the cumulative count for every number in the array (i.e. a number of element having values from 0 up to the current number inclusive).

    So, after invoking method init() on the instance of Range we would be able to find the number of elements in the range from a to b by looking at the values stored in the countingArray at corresponding indices. And that would have a cost O(1).

    public class Range {
        private int[] arr;
        private int[] counterArray;
        private int k;
        
        private Range(int[] arr, int k) { // constructor is not exposed
            this.arr = Arrays.copyOf(arr, arr.length);
            this.counterArray = new int[k + 1];
            this.k = k;
        }
        
        public static Range getInstance(int[] arr, int k) {
            Range range = new Range(arr, k);
            range.init();
            return range;
        }
        
        private void init() {
            sort();
            sumUpCount();
        }
        
        private void sort() {
    
            for (int i : arr) {
                counterArray[i]++;
            }
    
            int index = 0;
            int copy;
            for (int i = 0; i < counterArray.length; i++) {
                copy = counterArray[i];
                while (0 < counterArray[i]) {
                    arr[index++] = i;
                    counterArray[i]--;
                }
                counterArray[i] = copy;
            }
        }
        
        private void sumUpCount() {
            for (int i = 1; i < counterArray.length; i++) {
                counterArray[i] += counterArray[i - 1];
            }
        }
        
        public int query(int a, int b) {
            
            return a == 0 ? counterArray[b] :  counterArray[b] - counterArray[a - 1];
        }
    }
    

    main()

    public static void main(String[] args) {
        int[] a = {13,12,13,1,2,0,0,1,3,4};
        Range range = Range.getInstance(a, 13);
        System.out.print(range.query(1,4));
    }
    

    Output:

    5