Получение наименьшей возможной суммы из разности чисел
Я должен найти наименьшую возможную сумму из разности чисел.
Предположим, у меня есть 4 числа. 1515, 1520, 1500 и 1535 гг. Наименьшая сумма разницы равна 30, потому что 1535 - 1520 = 15 && 1515 - 1500 = 15 и 15 + 15 = 30. Если бы я так поступил ... : 1520 - 1515 = 5 && 1535 - 1500 = 35 это будет 40 в сумме. Надеюсь, вы поняли, если нет, спросите меня.Есть идеи, как это запрограммировать? Я просто нашел это в интернете, попытался перевести с моего языка на английский. Это звучит так интересный. Я не могу сделать bruteforce, потому что это займет годы, чтобы собрать. Мне не нужен код, только идеи, как программировать или небольшой фрагмент кода.
Спасибо.
Редактировать: Я не все выложил... Еще одно издание:
У меня есть, скажем, 8 возможных чисел. Но мне нужно взять только 6 из них, чтобы получить наименьшую сумму. Например, числа 1731, 1572, 2041, 1561, 1682, 1572, 1609, 1731
, наименьшая сумма будет равна 48, но здесь я должен взять только 6 чисел из 8.
10 ответов:
Решениеот marcog является правильным, нерекурсивным, полиномиальным решением проблемы-это довольно стандартная задача DP-но, просто для полноты, вот доказательство того, что она работает, и фактический код для этой проблемы. [@marcog: не стесняйтесь копировать любую часть этого ответа в свой собственный, если хотите; затем я удалю это.]
Доказательство
Пусть список будет x1, ..., x N . Предположим, что список отсортирован. Мы пытаемся найти K (непересекающихся) пар элементы из списка, так что сумма их различий минимизируется.
Утверждение : оптимальное решение всегда состоит из разностей последовательных элементов.
доказательство : предположим, что вы фиксируете подмножество элементов, различия которых взяты. Тогда по доказательству, данному Йонасом Келькером , оптимальное решениетолько для этого подмножества состоит из разностей последовательных элементов из списка. Теперь предположим, что существует решение, соответствующее a подмножество, которое не содержит пар последовательных элементов, т. е. решение включает разность x j -x i, где j>i+1. Тогда мы можем заменить x j на x i+1 , чтобы получить меньшую разницу, так как
x i ≤ x i+1 ≤ x j ⇒ x i+1-x i ≤ x j-x i.
(Излишне говорить, что если x i+1 =x j, то взятие x i+1 неотличимо от взятия x j.) Это доказывает претензия.Остальное-просто рутинное динамическое программирование: оптимальное решение, использующее k пар из первых n элементов, либо вообще не использует N-й элемент (в этом случае это просто оптимальное решение, использующее k пар из первых n-1), или он использует N-й элемент, и в этом случае это разность x n -x n-1 плюс оптимальное решение с использованием K-1 Пар из первого n-2.
Вся программа выполняется за время O (N log N + NK), как - говорит марког. (Сортировка + DP.)
Код
Вот полная программа. Я был ленив с инициализацией массивов и писал код Python, используя дикты; это небольшой коэффициент log (N) по сравнению с использованием реальных массивов.
''' The minimum possible sum|x_i - x_j| using K pairs (2K numbers) from N numbers ''' import sys def ints(): return [int(s) for s in sys.stdin.readline().split()] N, K = ints() num = sorted(ints()) best = {} #best[(k,n)] = minimum sum using k pairs out of 0 to n def b(k,n): if best.has_key((k,n)): return best[(k,n)] if k==0: return 0 return float('inf') for n in range(1,N): for k in range(1,K+1): best[(k,n)] = min([b(k,n-1), #Not using num[n] b(k-1,n-2) + num[n]-num[n-1]]) #Using num[n] print best[(K,N-1)]
Проверьте его:
Input 4 2 1515 1520 1500 1535 Output 30 Input 8 3 1731 1572 2041 1561 1682 1572 1609 1731 Output 48
Принимая во внимание правку:
Начните с сортировки списка. Затем используйте решение динамического программирования, с состоянием i, n , представляющее минимальную сумму N разностей при рассмотрении только первых i чисел в последовательности. Начальные состояния: dp[*][0] = 0, все остальное = бесконечность. Используйте два цикла: внешний цикл, проходящий через i от 1 до N, внутренний цикл, проходящий через n от 0 до R (3 в вашем примере edit-используется 3 пары чисел, что означает 6 отдельных чисел). Ваше рекуррентное отношение dp[i][n] = min(dp[i-1] [n], dp[i-2] [n-1] + seq[i] - seq[i-1]).
Вы должны знать об обработке граничных случаев, которые я проигнорировал, но общая идея должна работать и будет выполняться в O (N log N + NR ) и использовать O(NR) пространство.
Я предполагаю, что общая проблема заключается в следующем: учитывая список из 2n целых чисел, выведите список из n пар, такой, чтобы сумма |x-y| по всем парам (x, y) была как можно меньше.
В этом случае идея будет такой:
- сортировка чисел
- испускаем
(numbers[2k], numbers[2k+1])
дляk = 0, ..., n - 1
.Это работает. Доказательство:
Предположим, что у вас есть
x_1 < x_2 < x_3 < x_4
(возможно, с другими значениями между ними) и выход(x_1, x_3)
и(x_2, x_4)
. Тогда
|x_4 - x_2| + |x_3 - x_1| = |x_4 - x_3| + |x_3 - x_2| + |x_3 - x_2| + |x_2 - x_1| >= |x_4 - x_3| + |x_2 - x_1|
.В другими словами, всегда лучше выводить
(x_1, x_2)
и(x_3, x_4)
, потому что вы не перекрываете избыточно пространство междуx_2
иx_3
дважды. По индукции наименьшее число 2n должно быть сопряжено со вторым наименьшим числом; по индукции в остальной части списка сопряжение наименьших соседей всегда оптимально, поэтому предложенный мной алгоритм верен.
Упорядочьте список, затем выполните вычисление разницы.
EDIT: hi @hey
Эту задачу можно решить с помощью динамического программирования.
Скажем, у вас есть список
L
целых чиселN
, вы должны сформироватьk
пары (с2*k <= N
)Постройте функцию, которая находит наименьшее различие в списке (если список отсортирован, это будет быстрее ;) вызовите ее
smallest(list l)
Постройте еще один, который находит то же самое для двух пар (может быть сложно, но выполнимо) и назовите его
smallest2(list l)
Давайте определим
best(int i, list l)
функцию, которая дает наилучший результат для парi
в спискеl
Алгоритм выглядит следующим образом:
- Лучший(1, L) = наименьший (L)
- best (2, L) = smallest2 (L)
- для i от 1 до k:
Петля
Теперь проблема в том, что после того, как вы выбрали пару, два Инта, которые формируют границы, зарезервированы и не могут быть использованы для формирования лучшего решения. Но взглянув на два уровня назад вы можете гарантировать, что вы разрешили переключение кандидатов.compute min ( stored_best(i-2) - smallest2( stored_remainder(i-2) ), stored_best(i-1) - smallest( stored_remainder(i-1) ) and store as best(i) store the remainder as well for the chosen solution
(коммутационная работа выполняется
smallest2
)
Шаг 1: вычислить парные различия
Я думаю, что совершенно очевидно, что правильный подход заключается в сортировке чисел, а затем взять различия между ними. соседняя пара чисел. Эти различия являются" кандидатскими " различиями, способствующими минимальная сумма разницы. Использование чисел из вашего примера приведет к следующему:Number Diff ====== ==== 1561 11 1572 0 1572 37 1609 73 1682 49 1731 0 1731 310 2041
Сохраните различия в массиве, таблице или какой-либо другой структуре данных, где вы можете поддерживать различия и то и другое числа, которые способствовали каждой разнице. Назовем это
DiffTable
. Оно должно выглядеть примерно так:Index Diff Number1 Number2 ===== ==== ======= ======= 1 11 1561 1572 2 0 1572 1572 3 37 1572 1609 4 73 1609 1682 5 49 1682 1731 6 0 1731 1731 7 310 1731 2041
Шаг 2: Выберите минимальные различия
Если бы нужно было выбрать все числа, мы могли бы остановиться на шаге 1, выбрав пару чисел для нечетных чисел. индексы: 1, 3, 5, 7. Это правильный ответ. Однако, проблема заключается в том, что выбирается подмножество пар, и это несколько усложняет задачу. В вашем примере 3 различия (6 чисел = 3 пары = 3 различия) нужно выбрать такие, чтобы:
- сумма разностей минимальна
- числа, участвующие в любом выбранном различии, удаляются из списка.
Второй пункт означает, что если бы мы выбрали
Diff
11
(индекс = 1 выше), числа1561
и1572
являются удален из списка, и, следовательно, следующийDiff
из0
по индексу 2 не может быть использован, потому что только 1 экземпляр из1572
осталось. Всякий раз, когда aDiff
выбирается смежноеDiff
значение снимаются. Вот почему есть только один способ выбрать 4 пары числа из списка, содержащего восемь чисел.О единственном способе, который я могу придумать, чтобы минимизировать сумму
Diff
выше, - это генерировать и тестировать.Следующий псевдокод описывает процесс для генерации все "законные" наборы значений индекса для
DiffTable
произвольного размера где выбирается произвольное число пар чисел. Один (или несколько) из сгенерированные наборы индексов будут содержать индексы вDiffTable
давая минимальную суммуDiff
./* Global Variables */ M = 7 /* Number of candidate pair differences in DiffTable */ N = 3 /* Number of indices in each candidate pair set (3 pairs of numbers) */ AllSets = [] /* Set of candidate index sets (set of sets) */ call GenIdxSet(1, []) /* Call generator with seed values */ /* AllSets now contains candidate index sets to perform min sum tests on */ end procedure: GenIdxSet(i, IdxSet) /* Generate all the valid index values for current level */ /* and subsequent levels until a complete index set is generated */ do while i <= M if CountMembers(IdxSet) = N - 1 then /* Set is complete */ AllSets = AppendToSet(AllSets, AppendToSet(IdxSet, i)) else /* Add another index */ call GenIdxSet(i + 2, AppendToSet(IdxSet, i)) i = i + 1 end return
Функция
CountMembers
возвращает число членов в заданном множестве, функцияAppendToSet
возвращает новое множество где аргументы добавляются в один упорядоченный набор. НапримерAppendToSet([a, b, c], d)
возвращает множество:[a, b, c, d]
.Для заданных параметров, M = 7 и N = 3, Все множества становятся:
[[1 3 5] [1 3 6] <= Diffs = (11 + 37 + 0) = 48 [1 3 7] [1 4 6] [1 4 7] [1 5 7] [2 4 6] [2 4 7] [2 5 7] [3 5 7]]
Вычислите суммы, используя каждый набор индексов, тот, который является минимальным, идентифицирует требуемое число пар в
Это простая техника грубой силы, и она не очень хорошо масштабируется. Если бы у вас был список ... 50 пар чисел и хотел бы выбрать 5 пар, AllSets будет содержать 1,221,759 наборов количество пар для тестирования.DiffTable
. Выше я показываю, что второй набор из индексов дает тот минимум, который вы ищете.
Я знаю, что вы сказали, что вам не нужен код, но это лучший способ для меня описать решение, основанное на множестве. Решение работает под управлением SQL Server 2008. В код включены данные для двух приведенных вами примеров. Решение sql можно было бы сделать с одной самосоединяющейся таблицей, но мне легче объяснить, когда есть несколько таблиц.
--table 1 holds the values declare @Table1 table (T1_Val int) Insert @Table1 --this data is test 1 --Select (1515) Union ALL --Select (1520) Union ALL --Select (1500) Union ALL --Select (1535) --this data is test 2 Select (1731) Union ALL Select (1572) Union ALL Select (2041) Union ALL Select (1561) Union ALL Select (1682) Union ALL Select (1572) Union ALL Select (1609) Union ALL Select (1731) --Select * from @Table1 --table 2 holds the sorted numbered list Declare @Table2 table (T2_id int identity(1,1), T1_Val int) Insert @Table2 Select T1_Val from @Table1 order by T1_Val --table 3 will hold the sorted pairs Declare @Table3 table (T3_id int identity(1,1), T21_id int, T21_Val int, T22_id int, T22_val int) Insert @Table3 Select T2_1.T2_id, T2_1.T1_Val,T2_2.T2_id, T2_2.T1_Val from @Table2 AS T2_1 LEFT Outer join @Table2 AS T2_2 on T2_1.T2_id = T2_2.T2_id +1 --select * from @Table3 --remove odd numbered rows delete from @Table3 where T3_id % 2 > 0 --select * from @Table3 --show the diff values --select *, ABS(T21_Val - T22_val) from @Table3 --show the diff values in order --select *, ABS(T21_Val - T22_val) from @Table3 order by ABS(T21_Val - T22_val) --display the two lowest select TOP 2 CAST(T22_val as varchar(24)) + ' and ' + CAST(T21_val as varchar(24)) as 'The minimum difference pairs are' , ABS(T21_Val - T22_val) as 'Difference' from @Table3 ORDER by ABS(T21_Val - T22_val)
Я думаю, что подход @marcog можно упростить еще больше.
Возьмите базовый подход, что @Джонас-Колкер оказался для нахождения наименьшего различия. Возьмите полученный список и отсортируйте его. Возьмите самые маленькие записи из этого списка и используйте их в качестве отличий. Доказать, что это наименьшая сумма, тривиально.
Подход@marcog является эффективным O (N^2), потому что R == N является законным вариантом. Этот подход должен быть (2*(N log N))+N aka O(N log N).
Для этого требуется небольшая структура данных для хранения разницы и значений, из которых она была получена. Но, это константа на запись. Таким образом, пространство есть O (N).
Я бы пошел с ответом marcog, вы можете сортировать, используя любой из алгоритмов сортировки. Но сейчас анализировать особо нечего.
Если вы должны выбрать R чисел из N чисел так, чтобы сумма их различий была минимальной, то числа должны быть выбраны в последовательности, не пропуская никаких чисел между ними.
Следовательно, после сортировки массива вы должны выполнить внешний цикл от 0 до N-R и внутренний цикл от 0 до R-1 раз, чтобы вычислить сумму вычитать кривые.
Если нужно, вы должны попробовать с некоторыми примерами.
Я использовал подход, который использует рекурсивный алгоритм, но он действительно использует часть того, что внесли другие люди.
Прежде всего мы сортируем числа:
[1561,1572,1572,1609,1682,1731,1731,2041]
Затем мы вычисляем различия, отслеживая которые индексы чисел, внесших вклад в каждое различие:
Таким образом, мы получили 11, получив разницу между числом в индексе 0 и числом в индексе 1, 37 из чисел в индексах 2 и 3.[(11,(0,1)),(0,(1,2)),(37,(2,3)),(73,(3,4)),(49,(4,5)),(0,(5,6)),(310,(6,7))]
Затем я отсортировал этот список, так что он говорит мне, какие пары дают мне наименьшую разницу:
Здесь мы видим, что, учитывая, что мы хотим выбрать n чисел, наивным решением может быть выбор первых n / 2 пунктов этого списка. Беда в том, что в этом списке третий пункт разделяет индекс с первым, так что мы получим только 5 номеров, а не 6. В этом случае вам нужно выбрать четвертую пару, а также получить набор из 6 чисел. Отсюда я и придумал этот алгоритм. Повсюду есть набор принятых индексов, который начинается пустым, и есть ряд чисел, оставшихся для выбора n :[(0,(1,2)),(0,(5,6)),(11,(0,1)),(37,(2,3)),(49,(4,5)),(73,(3,4)),(310,(6,7))]
Это основная рутина, но жизнь не так проста. Есть случаи, которые мы еще не рассмотрели, но убедитесь, что вы поняли идею, прежде чем двигаться дальше.
- Если n равно 0, мы закончили.
- Если n равно 1, и первый элемент будет содержать только 1 индекс, которого нет в нашем наборе, мы взяли первый элемент, и все.
- Если n равно 2 или более, и первый элемент будет содержать 2 индекса, которых нет в нашем наборе, мы берем первый элемент и рекурсируем (например, goto 1). Этот время поиска n - 2 числа, которые составляют наименьшую разницу в остальной части списка.
на самом деле Шаг 3 неверен (обнаружилось, что непосредственно перед тем, как я опубликовал это: -/), так как может оказаться ненужным включать раннюю разницу для покрытия индексов, которые покрываются более поздними, существенными различиями. Первый пример ([1515, 1520, 1500, 1535]) это не соответствует действительности. Из-за этого я выбросил его в разделе ниже и расширил Шаг 4, чтобы иметь с ним дело.
Итак, теперь мы рассмотрим частные случаи:
- * * как указано выше * *
- * * как указано выше * *
- Если n равно 1, но первый элемент будет содержать два индекса, мы не можем его выбрать. Мы должны выбросить этот предмет и рекурсировать. На этот раз мы все еще ищем N индексов, и никаких изменений не произошло к нашему набору.
- Если n равно 2 или более, у нас есть выбор. Либо мы можем а) выбрать этот пункт и рекурсивно искать n - (1 или 2) индексы, либо б) пропустить этот пункт и рекурсивно искать n индексов.
4-это то, где он становится сложным, и где эта процедура превращается в поиск, а не просто в сортировку. Как мы можем решить, какую ветвь (a или b) выбрать? Ну, мы рекурсивны, так что давайте вызовем оба, и посмотрим, какой из них лучше. Как будет мы их судим?
Мы хотим взять ту ветвь, которая дает наименьшую сумму.
- ...но только в том случае, если он использует нужное количество индексов.
Таким образом, Шаг 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
Я закодировал это в Haskell (потому что я пытаюсь получить хороший результат). Я не уверен насчет публикации всего этого, потому что это может быть более запутанным, чем полезным, но вот основная часть:
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) плюс некоторая оболочка производит следующее:
[6] это стало долгим,но я постарался быть ясным. Надеюсь, это того стоило.*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
Edit : есть случай 3a, который можно добавить, чтобы избежать ненужной работы. Если текущая разница не содержит дополнительных индексов, ее можно пропустить. Об этом позаботились в шаге 4 выше, но нет смысла оценивать обе половины дерева без выгоды. Я добавил Это к Хаскеллу.
Что-то вроде
В вашем примере у вас есть 8 чисел и вам нужны лучшие 3 пары. Сначала отсортируйте список, который дает вам
- Список Сортировки
- Найти Дубликаты
- Сделайте дубликаты парой
- удалить дубликаты из списка
- разбить остальную часть списка на пары
- вычислить различия каждой пары
- Возьмите наименьшее количество
1561, 1572, 1572, 1609, 1682, 1731, 1731, 2041
Если у вас есть дубликаты, сделайте их парой и удалите их из списка, чтобы вы есть
[1572, 1572] = 0 [1731, 1731] = 0 L = { 1561, 1609, 1682, 2041 }
Разбейте оставшийся список на пары, давая вам следующие 4 пары
Затем отбросьте количество чисел, которое вам нужно.[1572, 1572] = 0 [1731, 1731] = 0 [1561, 1609] = 48 [1682, 2041] = 359
Это дает вам следующие 3 пары с самыми низкими парами
[1572, 1572] = 0 [1731, 1731] = 0 [1561, 1609] = 48
Итак
0 + 0 + 48 = 48