Среднее количество интервалов от входа в 0..N - PullRequest
5 голосов
/ 10 декабря 2010

Вопрос возник при рассмотрении вопроса «Найдите K пропущенных чисел в этом наборе, который должен охватывать [0..N]».

Автор вопроса попросил ответы CS вместо ответов на основе уравнений, и его предложение состояло в том, чтобы отсортировать входные данные, а затем выполнить итерацию по ним, чтобы перечислить пропущенные числа K.

Хотя мне кажется, что это нормально, но и расточительно. Давайте рассмотрим пример:

  • N = 200
  • K = 2 (рассмотрим K << N) </li>
  • недостающих элементов: 53, 75

«Сортированный» набор может быть представлен в виде: [0, 52] U [54, 74] U [76, 200], который намного компактнее, чем перечисление всех значений набора (и позволяет извлечь пропущенные числа в операциях O (K), для сравнения с O ( N) если набор отсортирован).

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

Итак, введем еще одну переменную: пусть I будет количеством элементов набора, который мы до сих пор передавали в структуру. Тогда в худшем случае мы можем иметь: min((N-K)/2, I) интервалы (я думаю ...)

Из чего мы выводим, что число интервалов, достигнутых во время построения, является максимальным для I в [0..N], наихудшим случаем является (N-K)/2, таким образом O(N).

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

Сколько интервалов ... в среднем?

1 Ответ

1 голос
/ 10 декабря 2010

Ваш подход против предложенного с сортировкой кажется классическим компромиссом: какие операции дешевы, а какие дорогостоящи.

Я нахожу вашу запись немного запутанной, поэтому, пожалуйста, позвольте мне использовать мою собственную:

Пусть S будет набором. Пусть n будет количеством предметов в наборе: n = |S|. Пусть max будет самым большим числом в наборе: max = max(S). Пусть k будет количеством элементов, не входящих в набор: k = |{0,...,max} \ S|.

Для решения по сортировке мы могли бы очень дешево вставить все элементы n в S, используя хеширование. Это займет ожидаемый O(n). Затем для поиска пропущенных элементов k мы сортируем набор в O(nlogn), а затем определяем пропущенные элементы в O(n).

То есть общая стоимость добавления n элементов, а затем поиска недостающих k элементов занимает ожидаемое O(n) + O(nlogn) + O(n) = O(nlogn).


Вы предлагаете другой подход, в котором мы представляем набор в виде списка плотных подмножеств S. Как бы вы реализовали такую ​​структуру данных? Я предлагаю отсортированное дерево (вместо списка), чтобы вставка стала эффективной. Потому что вы должны сделать для вставки нового элемента e? Я думаю, что вы должны:

  1. Найдите подмножество потенциальных кандидатов в дереве, куда можно добавить e
  2. Если подмножество уже содержит e, ничего не нужно делать.
  3. Если подмножество содержит e+1, а другое подмножество содержит e-1, объедините подмножества вместе и добавьте e к результату
  4. Если подмножество уже содержит e+1, но e-1 не содержится в S, добавьте e к этому подмножеству
  5. Если подмножество уже содержит e-1, но e+1 не содержится в S, добавьте e к этому подмножеству
  6. В противном случае создайте новое подмножество, содержащее только элемент e, и вставьте его в дерево.

Можно ожидать, что для поиска подмножеств, необходимых для вышеуказанных операций, потребуется O(logn). Операции 4. или 5. занимают постоянное время, если мы представляем подмножества в виде пар целых чисел (нам просто нужно уменьшить нижнюю границу или увеличить верхнюю границу). 3. и 6. потенциально требуют изменения древовидной структуры, но мы ожидаем, что это займет максимум O(logn), поэтому вся «вставка» не займет более O(logn).

Теперь, имея такую ​​структуру данных, мы можем легко определить пропущенные числа k, пройдя по дереву по порядку и собрав числа, которых нет ни в одном из подмножеств. Затраты линейны по количеству узлов в дереве, которое составляет <= n/2, поэтому общие затраты для этого составляют O(n).

Однако, если мы снова рассмотрим всю последовательность операций, мы получим n вставок O(nlogn) + O(n) для поиска k пропущенных чисел, поэтому общие затраты снова будут O(nlogn).

Это не лучше, чем ожидаемые затраты на первый алгоритм.


Третье решение - использовать логическое array для представления набора и одно целое число max для самого большого элемента в наборе.

Если элемент e добавлен в набор, вы устанавливаете array[e] = true. Вы можете реализовать переменный размер набора, используя расширение таблицы , поэтому затраты на вставку элемента в массив постоянны.

Чтобы извлечь недостающие элементы, вы просто собираете эти элементы f, где array[f] == false. Это займет O(max).

Таким образом, общие затраты на вставку n элементов и нахождение k отсутствующих элементов: O(n) + O(max). Тем не менее, max = n + k, и поэтому мы получаем как общие расходы O(n + k).


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

Сохраните ваш набор S в хэш-наборе, а также сохраните максимальный элемент в S в переменной max. Чтобы найти пропущенные k, сначала сгенерируйте результирующий набор R, содержащий все числа {0,...,max}. Затем выполните итерацию по S и удалите каждый элемент в S из R.

Расходы на это также O(n + k).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...