Подтвердить что ты не робот

Поиск двух не последующих элементов в массиве, сумма которых минимальна

Вступление:. Насколько я мог искать, этот вопрос еще не задавался в SO.
Это вопрос интервью.
Я даже не ищу программного кода, любой алгоритм/псевдокод будет работать.


Проблема: Задано целочисленным массивом int[] A и его размером N, найдите 2 не последующих (не могут быть смежными в массиве) элементы с минимальной суммой. Также ответ не должен содержать первый или последний элементы (индекс 0 и n-1). Также решение должно быть в O(n) сложности времени и пространства.

например. когда A = [5, 2, 4, 6, 3, 7] ответ 5, так как 2+3=5.
Когда A = [1, 2, 3, 3, 2, 1] ответ 4, так как 2+2=4 и вы не можете выбрать любой из 1, так как они находятся на концах массива.


Попытка: Сначала я думал, что один из числа в решении должен быть самым маленьким в массиве (кроме первого и последнего), но это было быстро опровергается встречным примером
A = [4, 2, 1, 2, 4] -> 4 (2+2)

Тогда я подумал, что если я найду наименьшие числа 2 (помимо первого и последнего) в массиве, то это будет два. Это, очевидно, быстро провалилось, потому что я не могу выбрать 2 соседних номера, и если мне нужно выбрать несмежные номера, это будет само определение вопроса:).

Наконец Я подумал, что я просто найду наименьшие числа 3 (помимо первого и последнего) в массиве, и решение должно быть два из тех, поскольку два из них не должны быть смежными друг с другом. Этот также не удалось из-за A = [2, 2, 1, 2, 4, 2, 6] -> 2+1=3, который, похоже, работает, потому что я найду 2, 1, 2, но если я нахожу 2, 1, 2 в индексах 1, 2, 3, это не будет обязательно (если бы я нашел именно 2 в индексе 5, но я не могу этого гарантировать).


Вопрос: Теперь я в тупике, может ли кто-нибудь придумать решение/идею, которая работает?

4b9b3361

Ответ 1

Вот реализация javascript в реальном времени алгоритма, который:

  • находит 4 наименьших элемента (исключая первый/последний элемент из поиска)
  • находит пары этих 4 элементов, которые не смежны в исходном массиве
  • находит из этих пар одно с минимальной суммой

function findMinNonAdjacentPair(a) {
    var mins = [];
    
    // quick exits:
    if (a.length < 5) return {error: "no solution, too few elements."};
    if (a.some(isNaN)) return {error: "non-numeric values given."};
    
    // collect 4 smallest values by their indexes    
    for (var i = 1; i < a.length - 1; i++) { // O(n)
        if (mins.length < 4 || a[i] < a[mins[3]]) {
            // need to keep record of this element in sorted list of 4 elements
            for (var j = Math.min(mins.length - 1, 2); j >= 0; j--) { // O(1)
                if (a[i] >= a[mins[j]]) break;
                mins[j+1] = mins[j];
            }
            mins[j+1] = i;
        }
    }
    // mins now has the indexes to the 4 smallest values

    // Find the smallest sum
    var result = {
        sum: a[mins[mins.length-1]]*2+1 // large enough
    }
    
    for (var j = 0; j < mins.length-1; j++) { // O(1)
        for (var k = j + 1; k < mins.length; k++) {
            if (Math.abs(mins[j] - mins[k]) > 1) { // not adjacent
                if (result.sum    > a[mins[j]]+a[mins[k]]) {
                    result.sum    = a[mins[j]]+a[mins[k]];
                    result.index1 = mins[j];
                    result.index2 = mins[k];
                };
                if (k < j + 3) return result; // cannot be improved
                break; // exit inner loop: it cannot bring improvement
            }
        }
    }
    return result;
}

// Get I/O elements
var input = document.getElementById('in');
var output = document.getElementById('out');
var select = document.getElementById('pre');

function process() {
    // translate input to array of numbers
    var a = input.value.split(',').map(Number);
    // call main function and display returned value
    output.textContent = JSON.stringify(findMinNonAdjacentPair(a), null, 4);
}

// respond to selection from list
select.onchange = function() {
    input.value = select.value;
    process();
}

// respond to change in input box
input.oninput = process;

// and produce result upon load:
process();
Type comma-separated list of values (or select one):</br>
<input id="in" value="2, 2, 1, 2, 4, 2, 6"> &lt;=
<select id="pre">
    <option value="5, 2, 4, 6, 3, 7">5, 2, 4, 6, 3, 7</option>
    <option value="1, 2, 3, 3, 2, 1">1, 2, 3, 3, 2, 1</option>
    <option value="4, 2, 1, 2, 4">4, 2, 1, 2, 4</option>
    <option value="2, 2, 1, 2, 4, 2, 6" selected>2, 2, 1, 2, 4, 2, 6</option>
</select>
</br>
Output:</br>
<pre id="out"></pre>

Ответ 2

  • Найдите наименьшее число рядом с первым и последним.
  • Найдите второй наименьший, который не является соседом первого, а не первым или последним в массиве. Затем постройте сумму.

    • Если первым элементом является второй или предпоследний элемент, у вас уже есть решение.
  • В противном случае вычислите сумму обоих соседей первого числа. проверьте, если его меньше, чем первая сумма

    • если нет: возьмите первую сумму
    • иначе возьмите второй

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

Ответ 3

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

Ответ 4

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

public static int[] minimumSumOfNonAcjacentElements(int[] a) {
    // the result for the sequence a[1:i]
    int minSum = Integer.MAX_VALUE;
    int minSumElement1 = Integer.MAX_VALUE;
    int minSumElement2 = Integer.MAX_VALUE;

    // the minimum element eligible for joining with a[i], i.e. from a[1 : i-2]
    int minElement = a[1];

    int prevElement = a[2]; // a[i - 1]
    for (int i = 3; i + 1 < a.length; i++) {
        int sum = minElement + a[i];
        if (sum < minSum) {
            minSum = sum;
            minSumElement1 = minElement;
            minSumElement2 = a[i];
        }

        if (prevElement < minElement) {
            minElement = prevElement;
        }
        prevElement = a[i];
    }

    return new int[] {minSumElement1, minSumElement2};
}

Вот несколько тестовых кодов, в которых угловые случаи из вопроса OP:

private static void test(int minSumIndex1, int minSumIndex2, int... input) {
    int[] result = minimumSumOfNonAcjacentElements(input);
    if (result[0] == minSumIndex1 && result[1] == minSumIndex2) {
        // ok
    } else {
        throw new AssertionError("Expected: " + minSumIndex1 + ", " + minSumIndex2 + ". Actual=" + Arrays.toString(result));
    }
}

public static void main(String[] args) throws Exception {
    test(2, 2, 4, 2, 1, 2, 4);
    test(1, 2, 2, 2, 1, 2, 4, 2, 6);
    test(1, 2, 0, 2, 1, 2, 4, 2, 0);
    System.out.println("All tests passed.");
}

Ответ 5

Используйте динамическое программирование.

  • Удалить или игнорировать первый и последний элементы вашего массива. Поскольку они не могут участвовать в решении, они не важны. Как только вы это сделаете, вы также можете игнорировать ограничение "не должно быть первым или последним", поскольку мы уже учли его.
  • Найти решение для первых трех элементов (что осталось от) массива (и без учета правила "нет первого/последнего элемента" ). В этом случае есть только одно решение (array[0] + array[2]), поэтому это тривиальный шаг.
  • Запомните минимальный элемент, который не является последним элементом (т.е. min(array[0], array[1])).
  • Найдите решение для первых четырех элементов. Нам не нужно переделывать всю проблему; вместо этого мы просто должны спросить, может ли введение четвертого элемента позволить нам создать меньшее решение. Мы можем сделать это, добавив четвертый элемент к минимальному элементу, который мы записали на предыдущем шаге, и сравнивая сумму с решением, которое мы нашли на втором этапе.
  • Обновить memoized минимальный элемент так, чтобы он был минимальным из первых трех элементов.
  • Продолжайте расширять и обновлять таким образом, пока мы не рассмотрим весь массив.

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

Ответ 6

Алгоритм:

  • Найдите минимум, избегая конечных индексов. (1 O (n)))
  • Найдите минимум, избегая концевых индексов и индекса (1) и соседних индексов. (1 O (n)))
  • Найти минимум, избегая концевых индексов и индекса (1) (1 O (n)))
  • Найдите минимум, избегая концевых индексов и индекса (3) и соседних индексов. (1 O (n)))
  • Возвращаем минимум сумм (1) + (2), (3) + (4), если они существуют.

Проходы 3 и 4 предназначены для передачи случая [4, 2, 1, 2, 4] = 4 путем нахождения обоих 2s.

public static int minSumNonAdjNonEnd(int[] array)
{
    // 1. Find minimum
    int minIdx1 = -1;
    int minValue1 = Integer.MAX_VALUE;
    for (int i = 1; i < array.length - 1; i++)
    {
        if (array[i] < minValue1)
        {
            minIdx1 = i;
            minValue1 = array[i];
        }
    }
    // 2. Find minimum not among (1) or adjacents.
    int minIdx2 = -1;
    int minValue2 = Integer.MAX_VALUE;
    for (int i = 1; i < array.length - 1; i++)
    {
        if ((i < minIdx1 - 1 || i > minIdx1 + 1) && (array[i] < minValue2))
        {
            minIdx2 = i;
            minValue2 = array[i];
        }
    }
    boolean sum1Exists = (minIdx1 > -1 && minIdx2 > -1);
    int sum1 = minValue1 + minValue2;

    // 3. Find minimum not among (1).
    int minIdx3 = -1;
    int minValue3 = Integer.MAX_VALUE;
    for (int i = 1; i < array.length - 1; i++)
    {
        if ((i != minIdx1) && (array[i] < minValue3))
        {
            minIdx3 = i;
            minValue3 = array[i];
        }
    }

    // 4. Find minimum not among(3) or adjacents.
    int minIdx4 = -1;
    int minValue4 = Integer.MAX_VALUE;
    for (int i = 1; i < array.length - 1; i++)
    {
        if ((i < minIdx3 - 1 || i > minIdx3 + 1) && (array[i] < minValue4))
        {
            minIdx4 = i;
            minValue4 = array[i];
        }
    }
    boolean sum2Exists = (minIdx3 > -1 && minIdx4 > -1);
    int sum2 = minValue3 + minValue4;

    if (sum1Exists)
    {
        if (sum2Exists)
            return Math.min(sum1, sum2);
        else
            return sum1;
    }
    else
    {
        if (sum2Exists)
            return sum2;
        else
            throw new IllegalArgumentException("impossible");
    }
}

Это выполняет 4 линейных поиска, для сложности O (n).

Тестовые случаи:

System.out.println(minSumNonAdjNonEnd(new int[] {5, 2, 4, 6, 3, 7}));
System.out.println(minSumNonAdjNonEnd(new int[] {1, 2, 3, 3, 2, 1}));
System.out.println(minSumNonAdjNonEnd(new int[] {4, 2, 1, 2, 4}));
System.out.println(minSumNonAdjNonEnd(new int[] {2, 2, 1, 2, 4, 2, 6}));
System.out.println(minSumNonAdjNonEnd(new int[] {2, 2, 3, 2}));

5
4
4
3
Exception in thread "main" java.lang.IllegalArgumentException: impossible

Ответ 7

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

  • Вы запускаете один раз над массивом, чтобы найти наименьший (O(n))
  • Вы запускаете второй раз, чтобы найти второй наименьший (O(n))
  • Если второе наименьшее не смежно с наименьшим, мы закончили (O(1) - просто проверка индекса)
  • В противном случае выполните третий раз, чтобы найти третий наименьший (все еще O(n))
  • Если не рядом с наименьшим наименьшим и наименьшим наименьшим возвратом в противном случае вернуть второй и третий наименьший

Ответ 8

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

После того, как найденное решение будет первой парой, разница в индексах будет более 1 и чья сумма будет наименьшей.

Решение является одним из (0,1) или (0,2) или (0,3) или (1,2) или (1,3) или (2,3), где значения указывают индексы массива, которые, в свою очередь, отслеживают положение фактических элементов в массиве.

Также вам нужно обработать специальный случай для длины массива 5 (arr\[1]+arr[3]) и ошибку для этих массивов меньше 5.

Ответ 9

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

static void printMinimalSum(int[] A) {  
    // Looking for mins so we init this with max value
    int[] mins = new int[]{Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
    // Indices, used just to print the solution
    int[] indices = new int[]{-1, -1, -1};
    // If the array has length 5 then there only one solution with the 2nd and 4th elements
    if (A.length == 5) {
        mins[0] = A[1];
        indices[0] = 1;
        mins[1] = A[3];
        indices[1] = 3;
    } else {        
        // Loop on the array without considering the first and the last element
        for (int i = 1; i < A.length - 1; i++) {
            // We consider each element which is smaller than its neighbours
            if ((i == 1 && A[i] < A[i + 1]) // 1: first element, compare it with the second one
                    || (i == A.length - 2 && A[i] < A[i - 1]) // 2: last element, compare it with the previous one
                    || (A[i] < A[i + 1] && A[i] < A[i - 1])) { // 3: mid element, compare it with both neighbors
                // If the element is "legal" then we see if it smaller than the 3 already saved
                if (A[i] < mins[0]) {
                    mins[0] = A[i];
                    indices[0] = i;
                } else if (A[i] < mins[1]) {
                    mins[1] = A[i];
                    indices[1] = i;
                } else if (A[i] < mins[2]) {
                    mins[2] = A[i];
                    indices[2] = i;
                }
            }
        }
    }     
    // Compute the 3 sums between those 3 elements
    int[] sums = new int[]{Math.abs(mins[0]+mins[1]), Math.abs(mins[0]+mins[2]), Math.abs(mins[1]+mins[2])};
    // Find the smaller sum and print it
    if (sums[0] < sums[1] || sums[0] < sums[2]){
        System.out.println("Sum = " + sums[0] + " (elements = {" + mins[0] + "," + mins[1] + "}, indices = {" + indices[0] + "," + indices[1] + "}");
    } else if (sums[1] < sums[0] || sums[1] < sums[2]){
        System.out.println("Sum = " + sums[1] + " (elements = {" + mins[0] + "," + mins[2] + "}, indices = {" + indices[0] + "," + indices[2] + "}");
    } else {
        System.out.println("Sum = " + sums[2] + " (elements = {" + mins[1] + "," + mins[2] + "}, indices = {" + indices[1] + "," + indices[2] + "}");
    }
}

public static void main(String[] args) {
    printMinimalSum(new int[]{5, 2, 4, 6, 3, 7});
    printMinimalSum(new int[]{1, 2, 3, 3, 2, 1});
    printMinimalSum(new int[]{4, 2, 1, 2, 4});
    printMinimalSum(new int[]{2, 2, 1, 2, 4, 2, 6});
}

Выход:

Sum = 5 (elements = {2,3}, indices = {1,4}
Sum = 4 (elements = {2,2}, indices = {1,4}
Sum = 4 (elements = {2,2}, indices = {1,3}
Sum = 3 (elements = {1,2}, indices = {2,5}

который кажется прекрасным.

Ответ 10

Как насчет этого: вы найдете k наименьшие числа (или, точнее, их индексы) (k достаточно большой, скажем 10). Уверен, что разыскиваемая пара находится между ними. Теперь вы просто проверяете возможные пары 50 и выбираете лучшее, которое удовлетворяет ограничениям.

Вам не нужно 10, меньше будет - но больше, чем 3:)

Изменить: поиск k наименьших чисел O(n), потому что вы просто сохраняете лучший 10, например, в куче (добавляете новый элемент, удаляете максимальные операции O(k*logk)=O(1)).

Тогда будет пара, которая удовлетворяет ограничениям (не рядом друг с другом). Также ясно, что если вы построите сумму с элементом не из этих k, это будет больше, чем лучшая пара, выбранная из этих элементов k.

Параметр не более пары k*k также O(1), поэтому все время работы O(n).

Ответ 11

Я думаю, что это должно сработать:

Найдите минимальный элемент 3 и их индексы. Поскольку все они не могут быть смежными, выберите 2 среди них.

Если все они смежны, а минимальное число находится посередине, повторяйте все элементы, найдите четвертый минимальный элемент, выберите минимум min1+min4, min2+min3, в зависимости от того, что меньше.

Вы можете сделать это и на одной итерации.

Ответ 12

Я использовал динамическое программирование для его решения.

Идея состоит в том, чтобы сначала создать массив, который отслеживает найденный минимум до сих пор, как показано ниже: Входной массив = [1, 3, 0, 5, 6] Минимальный массив = [1, 1, 0, 0, 0]

Теперь, используя минимальный массив и входной массив, мы можем использовать ниже:

DP[i] = min(DP[i-1], min(first_data, second_data))

где DP[i] означает найденный до сих пор минимум, который является суммой двух предыдущих альтернативных элементов.

first_data= сумма элемента current во входном массиве + сумма элемента current-2 в минимальном массиве

second_data= сумма элемента current-1 в массиве ввода + сумма элемента current-3 в минимальном массиве

    import random
    def get_min(numbers):
            #disregard the first and last element
            numbers = numbers[1:len(numbers)-1]
            #for remembering the past results
            DP = [0]*len(numbers)
            #for keeping track of minimum till now found
            table = [0]*len(numbers)
            high_number = 1 << 30

            min_number = numbers[0]
            table[0] = min_number
            for i in range(0, len(numbers)):
                    DP[i] = high_number
            for i in range(1, len(numbers)):
                    if numbers[i] < min_number:
                            min_number = numbers[i]
                            table[i] = numbers[i]
                    else:
                            table[i] = min_number
            for i in range(0, len(numbers)):
                    min_first, min_second = high_number, high_number
                    if i >= 2:
                            min_first = numbers[i] + table[i-2]
                    if i >= 3:
                            min_second = numbers[i-1] + table[i-3]
                    if i >= 1:
                            DP[i] = min(min(DP[i-1], min_first), min_second)
            return DP[len(numbers)-1]

    input = random.sample(range(100), 10)
    print(input)
    print(get_min(input))