Найти верхние k суммы двух отсортированных массивов - PullRequest
10 голосов
/ 15 февраля 2011

Вам даны два отсортированных массива размером n и m соответственно. Ваша задача (если вы решите ее принять) состоит в том, чтобы вывести наибольшие k сумм вида a[i]+b[j].

Решение O (k log k) можно найти здесь . Ходят слухи о решении O (k) или O (n). Существует ли один?

Ответы [ 4 ]

11 голосов
/ 15 февраля 2011

Я нашел ответы по вашей ссылке в основном расплывчаты и плохо структурированы.Вот начало с O (k * log (мин (m, n))) O (k * log (m + n)) O (k * log (k)) алгоритм.

Предположим, они отсортированы по убыванию.Представьте, что вы вычислили матрицу m * n сумм следующим образом:

for i from 0 to m
    for j from 0 to n
        sums[i][j] = a[i] + b[j]

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

q : priority queue (decreasing) := empty priority queue
add (0, 0) to q with priority a[0] + b[0]
while k > 0:
    k--
    x := pop q
    output x
    (i, j) : tuple of int,int := position of x
    if i < m:
        add (i + 1, j) to q with priority a[i + 1] + b[j]
    if j < n:
        add (i, j + 1) to q with priority a[i] + b[j + 1]

Анализ:

  1. Цикл выполняется k раз.
    1. На одну итерацию приходится одна операция pop.
    2. На одну итерацию можно добавить до двух операций вставки.
  2. Максимальный размер очереди с приоритетами равен O (мин (m, n)) O (m + n) O (k).
  3. Очередь приоритетов может быть реализована с помощью двоичной кучи, дающей журнал(размер) pop и insert.
  4. Следовательно, этот алгоритм равен O (k * log (min (m, n))) O (k * log (m + n)) O (k * log (k)).

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

Я мог бы написать это на Java, если есть какой-либо интерес.

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

1 голос
/ 15 февраля 2011
private static class FrontierElem implements Comparable<FrontierElem> {
    int value;
    int aIdx;
    int bIdx;

    public FrontierElem(int value, int aIdx, int bIdx) {
        this.value = value;
        this.aIdx = aIdx;
        this.bIdx = bIdx;
    }

    @Override
    public int compareTo(FrontierElem o) {
        return o.value - value;
    }

}

public static void findMaxSum( int [] a, int [] b, int k ) {
    Integer [] frontierA = new Integer[ a.length ];
    Integer [] frontierB = new Integer[ b.length ];
    PriorityQueue<FrontierElem> q = new PriorityQueue<MaxSum.FrontierElem>();
    frontierA[0] = frontierB[0]=0;
    q.add( new FrontierElem( a[0]+b[0], 0, 0));
    while( k > 0 ) {
        FrontierElem f = q.poll();
        System.out.println( f.value+"    "+q.size() );
        k--;
        frontierA[ f.aIdx ] = frontierB[ f.bIdx ] = null;
        int fRight = f.aIdx+1;
        int fDown = f.bIdx+1;
        if( fRight < a.length && frontierA[ fRight ] == null ) {
            q.add( new FrontierElem( a[fRight]+b[f.bIdx], fRight, f.bIdx));
            frontierA[ fRight ] = f.bIdx;
            frontierB[ f.bIdx ] = fRight;
        }
        if( fDown < b.length && frontierB[ fDown ] == null ) {
            q.add( new FrontierElem( a[f.aIdx]+b[fDown], f.aIdx, fDown));
            frontierA[ f.aIdx ] = fDown;
            frontierB[ fDown ] = f.aIdx;
        }
    }
}

Идея аналогична другому решению, но с наблюдением, что при добавлении в ваш набор результатов из матрицы на каждом шаге следующий элемент в нашем наборе может появиться только там, где текущий набор является вогнутым,Я назвал эти элементы пограничными элементами и отслеживаю их положение в двух массивах и их значения в очереди с приоритетами.Это помогает уменьшить размер очереди, но насколько мне еще предстоит выяснить.Кажется, это примерно sqrt( k ), но я не совсем уверен в этом.

(Конечно, массивы frontierA / B могут быть простыми логическими массивами, но таким образом они полностью определяют мой набор результатовнигде в этом примере не используется, но может быть полезным в противном случае.)

0 голосов
/ 26 сентября 2018

Большое спасибо @rlibby и @xuhdev с такой оригинальной идеей решения этой проблемы. У меня было похожее собеседование по программированию, которое требовало найти N наибольших сумм, образованных из K элементов в K по убыванию отсортированных массивов - это означает, что мы должны выбрать 1 элемент из каждого отсортированного массива, чтобы построить наибольшую сумму.

Example: List findHighestSums(int[][] lists, int n) {}

[5,4,3,2,1]
[4,1]
[5,0,0]
[6,4,2]
[1]

and a value of 5 for n, your procedure should return a List of size 5:

[21,20,19,19,18]

Ниже приведен мой код, пожалуйста, внимательно посмотрите на комментарии блока: D

private class Pair implements Comparable<Pair>{
    String state;

    int sum;

    public Pair(String state, int sum) {
        this.state = state;
        this.sum = sum;
    }

    @Override
    public int compareTo(Pair o) {
        // Max heap
        return o.sum - this.sum;
    }
}

List<Integer> findHighestSums(int[][] lists, int n) {

    int numOfLists = lists.length;
    int totalCharacterInState = 0;

    /*
     * To represent State of combination of largest sum as String
     * The number of characters for each list should be Math.ceil(log(list[i].length))
     * For example: 
     *      If list1 length contains from 11 to 100 elements
     *      Then the State represents for list1 will require 2 characters
     */
    int[] positionStartingCharacterOfListState = new int[numOfLists + 1];
    positionStartingCharacterOfListState[0] = 0;

    // the reason to set less or equal here is to get the position starting character of the last list
    for(int i = 1; i <= numOfLists; i++) {  
        int previousListNumOfCharacters = 1;
        if(lists[i-1].length > 10) {
            previousListNumOfCharacters = (int)Math.ceil(Math.log10(lists[i-1].length));
        }
        positionStartingCharacterOfListState[i] = positionStartingCharacterOfListState[i-1] + previousListNumOfCharacters;
        totalCharacterInState += previousListNumOfCharacters;
    }

    // Check the state <---> make sure that combination of a sum is new
    Set<String> states = new HashSet<>();
    List<Integer> result = new ArrayList<>();
    StringBuilder sb = new StringBuilder();

    // This is a max heap contain <State, largestSum>
    PriorityQueue<Pair> pq = new PriorityQueue<>();

    char[] stateChars = new char[totalCharacterInState];
    Arrays.fill(stateChars, '0');
    sb.append(stateChars);
    String firstState = sb.toString();
    states.add(firstState);

    int firstLargestSum = 0;
    for(int i = 0; i < numOfLists; i++) firstLargestSum += lists[i][0];

    // Imagine this is the initial state in a graph
    pq.add(new Pair(firstState, firstLargestSum));

    while(n > 0) {
        // In case n is larger than the number of combinations of all list entries 
        if(pq.isEmpty()) break;
        Pair top = pq.poll();
        String currentState = top.state;
        int currentSum = top.sum;

        /*
         * Loop for all lists and generate new states of which only 1 character is different from the former state  
         * For example: the initial state (Stage 0) 0 0 0 0 0
         * So the next states (Stage 1) should be:
         *  1 0 0 0 0
         *  0 1 0 0 0 (choose element at index 2 from 2nd array)
         *  0 0 1 0 0 (choose element at index 2 from 3rd array)
         *  0 0 0 0 1 
         * But don't forget to check whether index in any lists have exceeded list's length
         */
        for(int i = 0; i < numOfLists; i++) {
            int indexInList = Integer.parseInt(
                    currentState.substring(positionStartingCharacterOfListState[i], positionStartingCharacterOfListState[i+1]));
            if( indexInList < lists[i].length - 1) {
                int numberOfCharacters = positionStartingCharacterOfListState[i+1] - positionStartingCharacterOfListState[i];
                sb = new StringBuilder(currentState.substring(0, positionStartingCharacterOfListState[i]));
                sb.append(String.format("%0" + numberOfCharacters + "d", indexInList + 1));
                sb.append(currentState.substring(positionStartingCharacterOfListState[i+1]));
                String newState = sb.toString();
                if(!states.contains(newState)) {

                    // The newSum is always <= currentSum
                    int newSum = currentSum - lists[i][indexInList] + lists[i][indexInList+1];

                    states.add(newState);
                    // Using priority queue, we can immediately retrieve the largest Sum at Stage k and track all other unused states.
                    // From that Stage k largest Sum's state, then we can generate new states
                    // Those sums composed by recently generated states don't guarantee to be larger than those sums composed by old unused states.
                    pq.add(new Pair(newState, newSum));
                }

            }
        }
        result.add(currentSum);
        n--;
    }
    return result;
}

Позвольте мне объяснить, как я пришел к решению:

  1. Цикл while в моем ответе выполняется N раз, учитывая максимальную кучу (очередь с приоритетами).
  2. Операция опроса 1 раз со сложностью O (log ( sumOfListLength)) потому что максимальный элемент Pair in куча это sumOfListLength.
  3. Операции вставки могут выполняться до K раз, сложность для каждой вставки - log (sumOfListLength). Следовательно, сложность составляет O (N * log (sumOfListLength)) ,
0 голосов
/ 04 мая 2018

Поскольку предварительным условием является сортировка массива, давайте рассмотрим следующее для N = 5;

A [] = {1,2,3,4,5}

B [] = {496,497,498,499,500}

Теперь, поскольку мы знаем, что суммирование N-1 A & B будет наибольшим, следовательно, просто вставьте его в кучу вместе с индексами элемента A & B (почему, индексы? Мы скоро узнаем)

H.insert (А [N-1] + B [N-1], N-1, N-1);

сейчас

 while(!H.empty()) { // the time heap is not empty 

 H.pop(); // this will give you the sum you are looking for 

 The indexes which we got at the time of pop, we shall use them for selecting the next sum element.

 Consider the following :
 if we have i & j as the indexes in A & B , then the next element would be  max ( A[i]+B[j-1], A[i-1]+B[j], A[i+1]+B[j+1] ) , 
 So, insert the same if that has not been inserted in the heap
 hence
 (i,j)= max ( A[i]+B[j-1], A[i-1]+B[j], A[i+1]+B[j+1] ) ;
 if(Hash[i,j]){ // not inserted 
    H.insert (i,j);
 }else{
    get the next max from max ( A[i]+B[j-1], A[i-1]+B[j], A[i+1]+B[j+1] ) ; and insert.                      
 }

 K pop-ing them will give you max elements required.

Надеюсь, это поможет

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...