0

My problem statement is this - Find the count of numbers in a sorted array that are less than a given number, and this should be done efficiently with respect to time. I wrote a program using binary search that gets the count but time complexity wise it's failing. Need help in achieving this.

import java.util.Arrays;

public class SortedSearch {
    public static int countNumbers(int[] sortedArray, int lessThan) {
        if(sortedArray.length ==1 || sortedArray.length == 0) {
            return singleElement(sortedArray, lessThan);
        }
        else {
            return binarySearch(sortedArray, lessThan);
        }
    }

    public static int singleElement(int[] sortedArray, int searchVal) {
        if(sortedArray.length == 0) {
            return 0;
        }
        if(sortedArray[0] < searchVal) {
            return 1;
        }
        return 0;
    }

    private static int binarySearch(int[] sortedArray, int searchVal) {
        int low = 0;
        int high = (sortedArray.length)-1;
        int mid = (low + high)/2;
        if((sortedArray.length == 0) || (sortedArray[0] > searchVal)) {
            return 0;
        }
        if(sortedArray[high] < searchVal) {
            return sortedArray.length;
        }
        if(sortedArray[high] == searchVal) {
            return sortedArray.length-1;
        }
        if(sortedArray[mid] < searchVal) {
            int newLow = low;
            int newHigh = calculateNewHigh(sortedArray, newLow, 0, searchVal);
            int[] newArray = Arrays.copyOfRange(sortedArray, newLow, newHigh+1);
            return newArray.length;
        }
        else {
            int newLow = low;
            int newHigh = mid;
            int[] newArray = Arrays.copyOfRange(sortedArray, newLow, newHigh+1);
            return binarySearch(newArray, searchVal);
        }
    }

    private static int calculateNewHigh(int[] sortedArray, int low, int previousHigh, int searchVal) {
        int newHigh =  previousHigh + (sortedArray.length-low)/2;
        if(sortedArray[newHigh] < searchVal) {
            newHigh = calculateNewHigh(sortedArray, newHigh, newHigh, searchVal);
        }
        if(sortedArray[newHigh] == searchVal) {
            newHigh--;
        }
        if(sortedArray[newHigh] > searchVal) {
            newHigh--;
        }
        return newHigh;
    }

    public static void main(String[] args) {
        System.out.println(SortedSearch.countNumbers(new int[] { 1, 3, 5, 7 }, 4));
    }
}
  • Don't use `copyOfRange`. There is no need to copy a slice of the array, or create a new array at all, and this degrades the performance to O(n) instead of O(log n). – kaya3 Dec 22 '19 at 03:45

1 Answers1

0

Since you're using Arrays anyway, way not use the Arrays.binarySearch(int[] a, int key) method, instead of attempting to write your own?

public static int countNumbers(int[] sortedArray, int lessThan) {
    int idx = Arrays.binarySearch(sortedArray, lessThan);
    if (idx < 0)
        return -idx - 1; // insertion point
    while (idx > 0 && sortedArray[idx - 1] == lessThan)
        idx--;
    return idx; // index of first element with given value
}

The while loop1 is necessary because the javadoc says:

If the array contains multiple elements with the specified value, there is no guarantee which one will be found.

1) This loop is not optimal if e.g. all values are the same, see e.g. Finding multiple entries with binary search

Andreas
  • 154,647
  • 11
  • 152
  • 247