Поиск двух не последующих элементов в массиве, сумма которых минимальна
Вступление:. Насколько я мог искать, этот вопрос еще не задавался в 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
, но я не могу этого гарантировать).
Вопрос:
Теперь я в тупике, может ли кто-нибудь придумать решение/идею, которая работает?
Ответы
Ответ 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"> <=
<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))