Учитывая число current, найдите количество значений в массиве, которые больше и меньше, чем это значение.

//sort array for binary search
 int[] digits = Arrays.stream(sc.nextLine()
            .split(" "))
            .mapToInt(Integer::parseInt)
            .sorted()
            .toArray();

//for duplicate values, find higher index of current.

   while(low <= high){
        int mid = low + (high - low)/2;
        if(digits[mid] > current){
            high = mid - 1;
        }else if (digits[mid] == current){
            startindex = mid;
            high = mid - 1;   
        }else{
            startindex = mid;
            low = mid +1;
        }
    }

//for duplicate values, find lower index of current.
    int endindex = -1;
    low = 0;
    high = no_digits - 1;

    while(low <= high){
        int mid = low + (high - low)/2;
        if(digits[mid] > current){
            high = mid - 1;
        }else if (digits[mid] == current){
            endindex = mid;
            low = mid + 1;   
        }else{
            endindex = mid;
            low = mid + 1;
        }
    }

    System.out.println(endindex + "-" + startindex);

    if(digits[0] > current){
        smallest = 0;
        largest = no_digits;
        System.out.println(String.format("Smaller: %d, Greater: %d", smallest, largest));
    } else if (digits[no_digits - 1] < current){
        smallest = no_digits;
        largest = 0;
        System.out.println(String.format("Smaller: %d, Greater: %d", smallest, largest));
    }else {
        smallest = startindex;
        largest = no_digits - endindex - 1;                
        System.out.println(String.format("Smaller: %d, Greater: %d", smallest, largest));
    }
}

}

Пример ввода:

5 8 7 2 4 3 7 9 1 9 - Array of ints.

7
0
100
3
6

Выход:

Smaller: 5, Greater: 3
Smaller: 0, Greater: 10
Smaller: 10, Greater: 0
Smaller: 2, Greater: 7
Smaller: 5, Greater: 5

Мои результаты:

6-5 //start and end index.
Smaller: 5, Greater: 3
-1--1 
Smaller: 0, Greater: 10
9-9
Smaller: 10, Greater: 0
2-2
Smaller: 2, Greater: 7
4-4
Smaller: 5, Greater: 4

Мне удалось выдвинуть вышеупомянутый алгоритм, который учитывает значения больше или ниже, чем любое значение в массиве.

Однако я не могу найти решение для учета значений, которые не существуют в массиве, без итерации по массиву, так как мне нужно выполнить вышеизложенное за O ((N + Q) log N) времени.

В этом случае это будет последний тестовый случай, где значением является 6. 6 не существует в массиве, но мне все равно нужно посчитать все значения выше / ниже 6.

1
Carrein 27 Фев 2018 в 14:44

6 ответов

Лучший ответ

Алгоритм двоичного поиска создает «точку вставки» для значений, которые не существуют в массиве. Ваши startIndex и endIndex дадут вам первый «подходящий» предмет или тот, который находится рядом с ним. Другими словами, если вы ищете все значения меньше 6, поиск конечной точки даст индекс 5.

Обратите внимание, что вам не нужно использовать собственный алгоритм двоичного поиска: Java предоставляет вам реализацию.

Ссылка: Arrays.binarySearch

3
dasblinkenlight 27 Фев 2018 в 11:58

Так как вы все равно используете Stream, с map - вызовом не меньше, вы все равно итерируете весь массив.

Так что просто делай

class Counters {
    AtomicInteger smaller = new AtomicInteger(0);
    AtomicInteger larger = new AtomicInteger(0);
    private final int upperLimit;
    private final int lowerLimit;
    public Counters(int up, int down) {
        upperLimit = up;
        lowerLimit = down;
    }
    public void consider(int value) {
        if (value > upperLimit) larger.incrementAndGet();
        if (value < lowerLimit) smaller.incrementAndGet();
    }
    public int getSmaller() { return smaller.get(); }
    public int getLarger() { return larger.get(); }
}

Counters c = new Counters(upper, lower);
IntStream.of(yourValues).parallel().forEach(c::consider);
// your output here
System.out.printf("Smaller: %d - Larger: %d", c.getSmaller(), c.getLarger());

Или более общая версия

class MatchCounter<T> {
    AtomicInteger count = new AtomicInteger(0);
    private final Predicate<T> match;
    public MatchCounter(Predicate<T> m) { match = m; }
    public void consider(T value) {
        if (m.test(value)) { count.incrementAndGet(); }
    }
    public int getCount() { return count.get(); }
}

MatchCounter<Integer> smaller = new MatchCounter<>(i -> i < lower);
MatchCounter<Integer> larger = new MatchCounter<>(i -> i > upper);
Consumer<Integer> exec = smaller::consider;
Stream.of(yourArray).parallel().forEach(exec.andThen(larger::consider));
System.out.printf("Smaller: %d - Larger: %d", smaller.getCount(), larger.getCount());
2
daniu 27 Фев 2018 в 12:50

Вам просто нужно отфильтровать, а затем подсчитать вхождения. Например:

public static void main(String[] args) {
    int[] values = {5, 8, 7, 2, 4, 3, 7, 9, 1, 9};
    printCount(values, 7);
    printCount(values, 0);
    printCount(values, 100);
    printCount(values, 3);
    printCount(values, 6);

}
private static void printCount(int[] values, int value) {
    long smallerCount = Arrays.stream(values).filter(v -> v < value).count();
    long largerCount  = Arrays.stream(values).filter(v -> v > value).count();
    System.out.println(String.format("Smaller : %d, Larger: %d", smallerCount, largerCount));
}
0
Sébastien Helbert 27 Фев 2018 в 12:08

Итак, после редактирования вы заявляете, что хотите выполнить несколько запросов к одному и тому же массиву, чтобы время подготовки было менее важным.

Для этого создайте из массива красно-черное дерево; это даст вам отсортированную структуру, которая позволяет искать в O (log N).

Итак, что вы делаете для «меньшего» количества, так это идите влево, пока не найдете узел со значением, равным или большим, чем нижний предел; посчитайте всех оставленных детей этого. Аналог для большего (перейти вправо, найти равное или меньшее, считать справа).

Неважно, если элемент не присутствует в массиве, потому что вы ищете "равно или больше", так что если, например, 6 нет, но вы найдете 5, отсчитайте оттуда - только вы добавляете 1 к счету.

1
daniu 27 Фев 2018 в 13:07

См. массивы, которые могут пригодиться здесь.

void stats(int[] a, int sought) {
    a = Arrays.copyOf(a, a.length);
    Arrays.sort(a);
    int index = Arrays.binarySearch(a, sought);
    int smaller, larger;
    if (index < 0) {
        // Not found.
        index = ~index; // Insertion position.
        smaller = index;
        larger = index:
    } else {
        // Found.
        smaller = index;
        while (smaller > 0 && a[smaller] == sought) {
            --smaller;
        }
        while (index <= 0 && a[index] == sought) {
            ++index;
        }
    }
    larger = a.length - index;
    int equals = index - smaller;
    System.out.printf("Smaller %d, equal %d, larger %d.%n",
            smaller, equals, larger); 
}

Как вы видите, при поиске элемента достаточно было бы вернуться к O (N), что меньше, чем сортировка O (N log N).

Быстрее - O (log N) вместо O (N) для этой части - было бы, если бы можно было выполнить бинарный поиск по искомому - 0,5 и искомому + 0,5.

void stats(int[] a, int sought) {
    a = Arrays.copyOf(a, a.length);
    for (int i = 0; i < a.length; ++i) {
        a[i] *= 2;
    }
    Arrays.sort(a);
    int smallerI = Arrays.binarySearch(a, 2 * sought - 1);
    int largerI = Arrays.binarySearch(a, 2 * sought + 1);
    int smaller = ~smallerI;
    int larger = a.length - ~largerI;
    int equals = ~largerI - ~smallerI;
    System.out.printf("Smaller %d, equal %d, larger %d.%n",
            smaller, equals, larger); 
}

При этом используются удвоенные целые числа, что имеет недостаток в том, что допустимый домен значений массива уменьшается вдвое.

В вашем случае ваш собственный алгоритм двоичного поиска должен выбрать этот последний случай (без удвоения), используя неявный sought + 0.5, никогда не находящий, ищущий позицию вставки.

1
Joop Eggen 27 Фев 2018 в 12:24

РЕДАКТИРОВАТЬ . Вопрос был отредактирован, и теперь в нем содержится дополнительное требование, согласно которому алгоритм должен работать быстро для нескольких запросов, точнее: общее время выполнения должно быть O((N + Q) * log(N)), где N - размер массива и Q - количество запросов.

Подход ниже работает только для Q = 1.


Я не вижу причин не делать это за линейное O(N) время.

// get this from scanner
int number = 5;
int[] array = {6, 2, 7, 4, 1, 42};

// the "algorithm"
int numLessThan = 0;
int numGreaterThan = 0;
for (int i: array) {
  if (i < number) numLessThan++;
  if (i > number) numGreaterThan++;
}
System.out.println(
  "Num greater than: " + numGreaterThan + " " +
  "Num less than: " + numLessThan
);

Выход:

Num greater than: 3 Num less than: 3

Если вы настаиваете на том, чтобы делать это с помощью потоков:

long numLessThan = Arrays.stream(array).filter(x -> x < number).count();
long numGreaterThan = Arrays.stream(array).filter(x -> x > number).count();

Даже если он дважды пересекает массив, он все равно O(N).

2
Andrey Tyukin 27 Фев 2018 в 12:33