Я выбрал подход, в котором используется рекурсивный алгоритм, но для этого требуется то, что внесли другие люди.
Прежде всего мы сортируем числа:
[1561,1572,1572,1609,1682,1731,1731,2041]
Затем мы вычисляем различия, отслеживая индексы чисел, которые повлияли на каждую разницу:
[(11,(0,1)),(0,(1,2)),(37,(2,3)),(73,(3,4)),(49,(4,5)),(0,(5,6)),(310,(6,7))]
Таким образом, мы получили 11, получив разницу между числом в индексе 0 и числом в индексе 1, 37 из чисел в индексах 2 и 3.
Затем я отсортировал этот список, и он говорит мне, какие пары дают мне наименьшее различие:
[(0,(1,2)),(0,(5,6)),(11,(0,1)),(37,(2,3)),(49,(4,5)),(73,(3,4)),(310,(6,7))]
Здесь мы можем видеть, что, учитывая, что мы хотим выбрать n чисел, наивным решением может быть выбор первых n / 2 элементов этого списка. Проблема в том, что в этом списке третий элемент имеет общий индекс с первым, поэтому на самом деле мы получили бы только 5 чисел, а не 6. В этом случае вам также нужно выбрать четвертую пару, чтобы получить набор из 6 чисел.
Отсюда я придумал этот алгоритм. Повсюду есть набор принятых индексов, который начинается пустым, и есть несколько чисел, которые можно выбрать n :
- Если n равно 0, все готово.
- , если n равно 1, и первый элемент предоставит только 1 индекс, которого нет в нашем наборе, мы взяли первый элемент и все готово.
- , если n равно 2 или более, и первый элемент предоставит 2 индекса, которых нет в нашем наборе, мы взяли первый элемент и рекурсивно (например, перейдем к 1). На этот раз ищем n - 2 числа, которые имеют наименьшую разницу в остальной части списка.
Это основная рутина, но жизнь не так проста. Есть случаи, которые мы еще не рассмотрели, но перед тем, как идти дальше, убедитесь, что вы поняли идею.
На самом деле шаг 3 является неправильным (обнаружил, что непосредственно перед тем, как я опубликовал это: - /), так как может быть ненужным включать раннюю разницу, чтобы охватить индексы, которые покрываются более поздними существенными различиями. Первый пример ([1515, 1520, 1500, 1535]) противоречит этому. Из-за этого я выбросил его в следующем разделе и расширил шаг 4, чтобы разобраться с этим.
Итак, теперь мы рассмотрим специальные случаи:
- ** как указано выше **
- ** как указано выше **
- Если n равно 1, но первый элемент будет содержать два индекса, мы не можем его выбрать. Мы должны выбросить этот предмет и рекурсировать. На этот раз мы все еще ищем индексы n , и в нашем принятом наборе изменений не было.
- Если n равно 2 или более, у нас есть выбор. Либо мы можем а) выбрать этот элемент и рекурсивно искать индексы n - (1 или 2), либо б) пропустить этот элемент и рекурсивно искать индексы n .
4 - это то, где это становится сложным, и где эта рутина превращается в поиск, а не просто в сортировку. Как мы можем решить, какую ветку (а или б) взять? Ну, мы рекурсивны, так что давайте назовем оба и посмотрим, какой из них лучше. Как мы будем судить их?
- Мы захотим взять ту ветку, которая даст самую низкую сумму.
- ... но только если он будет использовать нужное количество индексов.
Таким образом, шаг 4 становится примерно таким (псевдокод):
x = numberOfIndicesProvidedBy(currentDifference)
branchA = findSmallestDifference (n-x, remainingDifferences) // recurse looking for **n-(1 or 2)**
branchB = findSmallestDifference (n , remainingDifferences) // recurse looking for **n**
sumA = currentDifference + sumOf(branchA)
sumB = sumOf(branchB)
validA = indicesAddedBy(branchA) == n
validB = indicesAddedBy(branchB) == n
if not validA && not validB then return an empty branch
if validA && not validB then return branchA
if validB && not validA then return branchB
// Here, both must be valid.
if sumA <= sumB then return branchA else return branchB
Я закодировал это на Хаскеле (потому что я стараюсь в этом преуспеть). Я не уверен насчет публикации всего этого, потому что это может быть более запутанным, чем полезным, но вот основная часть:
findSmallestDifference = findSmallestDifference' Set.empty
findSmallestDifference' _ _ [] = []
findSmallestDifference' taken n (d:ds)
| n == 0 = [] -- Case 1
| n == 1 && provides1 d = [d] -- Case 2
| n == 1 && provides2 d = findSmallestDifference' taken n ds -- Case 3
| provides0 d = findSmallestDifference' taken n ds -- Case 3a (See Edit)
| validA && not validB = branchA -- Case 4
| validB && not validA = branchB -- Case 4
| validA && validB && sumA <= sumB = branchA -- Case 4
| validA && validB && sumB <= sumA = branchB -- Case 4
| otherwise = [] -- Case 4
where branchA = d : findSmallestDifference' (newTaken d) (n - (provides taken d)) ds
branchB = findSmallestDifference' taken n ds
sumA = sumDifferences branchA
sumB = sumDifferences branchB
validA = n == (indicesTaken branchA)
validB = n == (indicesTaken branchA)
newTaken x = insertIndices x taken
Надеюсь, вы сможете увидеть все дела там. Этот код (-ish), плюс некоторая обертка производит это:
*Main> findLeastDiff 6 [1731, 1572, 2041, 1561, 1682, 1572, 1609, 1731]
Smallest Difference found is 48
1572 - 1572 = 0
1731 - 1731 = 0
1572 - 1561 = 11
1609 - 1572 = 37
*Main> findLeastDiff 4 [1515, 1520, 1500,1535]
Smallest Difference found is 30
1515 - 1500 = 15
1535 - 1520 = 15
Это стало длинным, но я пытался быть явным. Надеюсь, это того стоило.
Редактировать : Существует случай 3а, который можно добавить, чтобы избежать ненужной работы. Если текущая разница не дает дополнительных индексов, ее можно пропустить. Об этом позаботятся в шаге 4 выше, но нет смысла оценивать обе половины дерева без какой-либо выгоды. Я добавил это в Haskell.