Эффективно получать отсортированные суммы отсортированного списка

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

Чтобы было ясно, меня интересует алгоритм. Не стесняйтесь размещать код на любом языке и в любой парадигме, которые вам нравятся.

Ответов (8)

Решение

Изменить с 2018 года: вам, вероятно, стоит перестать это читать. (Но я не могу удалить его, поскольку он принят.)

Если выписать суммы так:

1 4  5  6  8  9
---------------
2 5  6  7  9 10
  8  9 10 12 13
    10 11 13 14
       12 14 15
          16 17
             18

Вы заметите, что, поскольку M [i, j] <= M [i, j + 1] и M [i, j] <= M [i + 1, j], вам нужно только изучить верхний левый угол " углы »и выберите самый низкий.

например

  • только 1 верхний левый угол, выберите 2
  • только 1, выберите 5
  • 6 или 8, выберите 6
  • 7 или 8, выберите 7
  • 9 или 8, выберите 8
  • 9 или 9, выберите оба :)
  • 10 или 10 или 10, выберите все
  • 12 или 11, выберите 11
  • 12 или 12, выберите оба
  • 13 или 13, выберите оба
  • 14 или 14, выберите оба
  • 15 или 16, выберите 15
  • только 1, выбери 16
  • только 1, выберите 17
  • только 1, выберите 18

Конечно, когда у вас много левых верхних углов, это решение переходит на второй план.

Я почти уверен, что это проблема Ω (n²), потому что вам нужно вычислить суммы для каждого M [i, j] - если только у кого-то нет лучшего алгоритма для суммирования :)

Если вы ищете действительно языково-независимое решение, то, на мой взгляд, вы будете сильно разочарованы, потому что застрянете с циклом for и некоторыми условными операторами. Однако, если вы открыли его для функциональных языков или функций функционального языка (я смотрю на вас, LINQ), мои коллеги могут заполнить эту страницу элегантными примерами на Ruby, Lisp, Erlang и других.

Независимо от того, что вы делаете, без дополнительных ограничений на входные значения вы не можете добиться большего, чем O (n ^ 2), просто потому, что вам нужно перебирать все пары чисел. Итерация будет доминировать при сортировке (что вы можете сделать за O (n log n) или быстрее).

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

Мой алгоритм в Haskell:

matrixOfSums list = [[a+b | b <- list, b >= a] | a <- list]

sortedSums = foldl merge [] matrixOfSums

--A normal merge, save that we remove duplicates
merge xs [] = xs
merge [] ys = ys
merge (x:xs) (y:ys) = case compare x y of
    LT -> x:(merge xs (y:ys))
    EQ -> x:(merge xs (dropWhile (==x) ys))
    GT -> y:(merge (x:xs) ys)

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

-- wide-merge does a standard merge (ala merge-sort) across an arbitrary number of lists
-- wideNubMerge does this while eliminating duplicates
wideNubMerge :: Ord a => [[a]] -> [a]
wideNubMerge ls = wideNubMerge1 $ filter (/= []) ls
wideNubMerge1 [] = []
wideNubMerge1 ls = mini:(wideNubMerge rest)
    where mini = minimum $ map head ls
          rest = map (dropWhile (== mini)) ls

betterSortedSums = wideNubMerge matrixOfSums

Однако, если вы знаете, что собираетесь использовать все суммы, и нет никаких преимуществ получить некоторые из них раньше, используйте ' foldl merge []', так как это быстрее.

Я полагаю, что вместо того, чтобы кодировать это, я буду псевдокодировать его поэтапно и объяснять свою логику, чтобы более опытные программисты могли при необходимости вытащить дыры в моей логике.

На первом этапе мы начинаем со списка чисел длиной n. Для каждого числа нам нужно создать список длиной n-1, потому что мы не добавляем число к самому себе. К концу у нас есть список из примерно n отсортированных списков, которые были сгенерированы за время O (n ^ 2).

step 1 (startinglist) 
for each number num1 in startinglist
   for each number num2 in startinglist
      add num1 plus num2 into templist
   add templist to sumlist
return sumlist 

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

step 2 (sumlist) 
create an empty list mergedlist
for each list templist in sumlist
   set mergelist equal to: merge(mergedlist,templist)
return mergedlist

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

Итак, вот мое решение. Весь алгоритм занимает время O (n ^ 2). Не стесняйтесь указывать на любые ошибки или улучшения.

В SQL:

create table numbers(n int not null)
insert into numbers(n) values(1),(1), (2), (2), (3), (4)


select distinct num1.n+num2.n sum2n
from numbers num1
inner join numbers num2 
    on num1.n<>num2.n
order by sum2n

C# LINQ:

List<int> num = new List<int>{ 1, 1, 2, 2, 3, 4};
var uNum = num.Distinct().ToList();
var sums=(from num1 in uNum
        from num2 in uNum 
        where num1!=num2
        select num1+num2).Distinct();
foreach (var s in sums)
{
    Console.WriteLine(s);
}

Вы можете сделать это в двух строках на Python с помощью

allSums = set(a+b for a in X for b in X)
allSums = sorted(allSums)

Стоимость этого составляет n^2 (может быть, дополнительный коэффициент регистрации для набора?) Для итерации и s * log (s) для сортировки, где s - размер набора.

Размер набора может быть таким большим, как n*(n-1)/2 например, если X = [1,2,4,...,2^n] . Поэтому, если вы хотите сгенерировать этот список, это потребуется, по крайней мере, n^2/2 в худшем случае, поскольку это размер вывода.

Однако, если вы хотите выбрать первые k элементов результата, вы можете сделать это за O (kn), используя алгоритм выбора для отсортированных X+Y матриц Фредериксона и Джонсона ( подробности см. Здесь) . Хотя это, вероятно, можно изменить, чтобы сгенерировать их онлайн, повторно используя вычисления и получив эффективный генератор для этого набора.

@deuseldorf, Питер. Есть некоторая путаница в том, что (n!) я серьезно сомневаюсь, что deuseldorf имел в виду "n факториал", а просто "n, (очень взволнован)!"

Этот вопрос ломал мне голову уже около дня. Потрясающие.

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

Если вы посмотрите на все списки, которые вы создаете, они имеют следующий вид:

(a[i], a[j]) | j>=i

Если перевернуть его на 90 градусов, получится:

(a[i], a[j]) | i<=j

Теперь процесс слияния должен принимать два списка i и i+1 (которые соответствуют спискам, где первым членом всегда является a[i] и a[i+1] ), вы можете связать диапазон для вставки элемента (a[i + 1], a[j]) в список i по местоположению (a[i], a[j]) и расположению (a[i + 1], a[j + 1]) .

Это означает, что вам следует выполнить слияние в обратном порядке с точки зрения j . Я не знаю (пока), можете ли вы это использовать j, но это кажется возможным.