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

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

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

Ответ 1

Изменить с 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] - если у кого-то нет лучшего алгоритма для суммирования:)

Ответ 2

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

На первом шаге мы начинаем с списка чисел 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) времени. Не стесняйтесь указывать на любые ошибки или улучшения.

Ответ 3

Вы можете сделать это в двух строках в 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 Фредериксоном и Джонсоном (см. здесь подробности gory). Хотя это, вероятно, можно изменить, чтобы сгенерировать их в сети, повторно используя вычисления и получить эффективный генератор для этого набора.

@deuseldorf, Питер Существует некоторая путаница (n!), Я серьезно сомневаюсь, что deuseldorf означал "n factorial", но просто "n, (очень возбужденный)!"

Ответ 4

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

Мой алгоритм, в 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 []', как это быстрее.

Ответ 5

В 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

С# 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);
}

Ответ 6

Этот вопрос уже несколько дней разрушает мой мозг. Высокий.

В любом случае вы не можете легко избавиться от природы 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, но это кажется возможным.

Ответ 7

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

Ответ 8

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