O (nlogn) Алгоритм. Найдите три равномерно распределенных внутри двоичной строки.

У меня был этот вопрос на вчерашнем анализе алгоритмов, и я не могу понять ответ. Это сводит меня с ума, потому что это стоило около 40 очков. Я полагаю, что большая часть класса не решила его правильно, потому что за последние 24 часа у меня не было решения.

Для произвольной двоичной строки длины n найдите три равномерно расположенных внутри строки, если они существуют. Напишите алгоритм, который решает это в O (n * log (n)) времени.

Таким образом, такие строки имеют три "равномерно распределенных": 11100000, 0100100100

edit: Это случайное число, поэтому оно должно работать для любого числа. Я привел примеры, иллюстрирующие свойство "равномерно распределенного". Таким образом, 1001011 является действительным числом. С 1, 4 и 7 являются равномерными.

Ответ 1

Наконец-то! Следуя указаниям sdcvvc answer, мы имеем: алгоритм O (n log n) для проблемы! Это просто, когда вы это понимаете. Те, кто догадывался о БПФ, были правы.

Проблема: нам задана двоичная строка S длины n, и мы хотим найти в ней три равномерно расположенных 1s. Например, S может быть 110110010, где n = 9. Он имеет равномерно расположенные 1s в положениях 2, 5 и 8.

  • Сканируйте S слева направо и создайте список L для позиций 1. Для S=110110010 выше мы имеем список L = [1, 2, 4, 5, 8], Этот шаг равен O (n). Теперь проблема состоит в том, чтобы найти арифметическую прогрессию длины 3 в L, т.е. Найти различные a, b, c в L такие, что ba = cb или эквивалентно a + с = 2b. В приведенном выше примере мы хотим найти прогрессию (2, 5, 8).

  • Сделайте многочлен p с членами x k для каждого k из L. Для примера выше сделаем многочлен p (x) = (x + x 2 + x 4 + x 5 + x 8). Этот шаг - O (n).

  • Найдите многочлен q= p 2 используя Fast Fourier Transform. В приведенном выше примере мы получаем многочлен q (x) = x 16 + 2x 13 + 2x 12 + 3x 10 + 4x 9 + x 8 + 2x 7 + 4x 6 + 2x 5 + x 4 + 2x 3 + x 2. Этот шаг - O (n log n).

  • Игнорировать все члены, кроме тех, которые соответствуют x 2k для некоторого k в L. В приведенном выше примере мы получаем члены х 16 3x 10 x 8 x 4 x 2. Этот шаг - O (n), если вы решите сделать это вообще.

Здесь критическая точка: коэффициент для любого x 2b для b in L - это точно число пар (a, c) в L таких, что a + c = 2b. [CLRS, Ex. 30.1-7] Одна такая пара (b, b) всегда (поэтому коэффициент равен не менее 1), но если существует какая-либо другая пара (a, c), то коэффициент равен не менее 3, из (a, c ) и (с, а). В приведенном выше примере коэффициент х 10 равен 3 именно из-за AP (2,5,8). (Эти коэффициенты x 2b всегда будут нечетными числами по вышеприведенным причинам, а все остальные коэффициенты в q всегда будут четными.)

Итак, алгоритм должен смотреть на коэффициенты этих членов x 2b и видеть, если какое-либо из них больше 1. Если их нет, то нет равномерно расположенных 1s, Если в L существует ab, для которого коэффициент при x 2b больше 1, то мы знаем, что существует некоторая пара (a, c) - отличная от (b, b) - для a + c = 2b. Чтобы найти фактическую пару, мы просто попробуем каждый a в L (соответствующий c будет 2b-a) и посмотрим, есть ли 1 в позиции 2b-a в S. Этот шаг равен O (n).

Что все, люди.


Можно спросить: нужно ли нам использовать БПФ? Многие ответы, такие как бета-версия, flybywire's и rsp's, предположим, что подход, который проверяет каждую пару из 1s и видит, есть ли 1 в "третьем" положении, может работать в O (n log n), исходя из интуиции, что, если их слишком много, мы легко найдем тройку, и если их будет слишком мало, проверка всех пар займет мало времени. К сожалению, хотя эта интуиция правильная и простой подход лучше, чем O (n 2), это не намного лучше. Как и в sdcvvc answer, мы можем взять "кантороподобный набор" строк длины n = 3 k с 1s в позициях чье тройное представление имеет только 0s и 2s (нет 1s) в нем. Такая строка имеет в ней 2 k= n (log 2)/(log 3) ≈ n 0,63 единицы и не равномерно распределенные 1s, поэтому проверка всех пар будет порядка квадрата числа 1s в нем: 4 k ≈ n 1,26 который, к сожалению, асимптотически намного больше (n log п). На самом деле худший случай еще хуже: Лео Мозер в 1953 году построил (эффективно) такие строки, которые имеют n 1-c/√ (log n) 1s в них, но не равномерно распределенных 1s, что означает, что на таких строках простой подход будет принимать Θ (n 2-2c/√ (log n)) - только крошечный бит лучше, чем Θ (n 2), удивительно!


О максимальном числе 1s в строке длины n без 3 равномерно расположенных (которые мы видели выше, было как минимум n 0,63 из простой канторной конструкции и по крайней мере n 1-c/√ (log n) с конструкцией Мозера) - это OEIS A003002. Он также может быть вычислен непосредственно из OEIS A065825 как k, такой, что A065825 (k) ≤ n < A065825 (к + 1). Я написал программу, чтобы найти их, и получается, что жадный алгоритм не дает самой длинной такой строки. Например, для n = 9 мы можем получить 5 1s (110100011), но жадный дает только 4 (110110000), для n = 26 мы можем получить 11 1s (11001010001000010110001101), но жадные дают только 8 (11011000011011000000000000), а для n = 74, мы можем получить 22 1 с (11000010110001000001011010001000000000000000010001011010000010001101000011), но жадные дают только 16 (110110000110110000000000000110110000110110000000000000000000000000000000000000). Они соглашаются в довольно многих местах до 50 (например, все от 38 до 50). Как говорят ссылки OEIS, кажется, что Jaroslaw Wroblewski заинтересован в этом вопросе, и он ведет веб-сайт на этих наборах без усреднения. Точные числа известны только до 194.

Ответ 2

Ваша проблема называется AVERAGE в этой статье (1999):

Задача 3SUM-жесткая, если существует субквадратичная редукция из задачи 3SUM: если задано множество A из n целых чисел, существуют ли элементы a, b, c в такие, что a + b + c = 0? Неизвестно, является ли СРЕДНИЙ 3SUM-жестким. Тем не менее, существует простое линейное сокращение времени от AVERAGE до 3SUM, описание которого мы опускаем.

Wikipedia:

Когда целые числа находятся в диапазоне [-u... u], 3SUM может быть решена за время O (n + u lg u), представляя S в виде битового вектора и выполняя свертку с использованием FFT.

Этого достаточно, чтобы решить вашу проблему:).

Что важно очень, так это то, что O (n log n) является сложностью в терминах числа нулей и единиц, а не количеством единиц (которые могут быть заданы как массив, например [1, 5,9,15]). Проверка того, имеет ли набор арифметическую прогрессию, термины числа 1, является трудной, и согласно этой статье с 1999 года не существует более быстрого алгоритма, чем O (n 2), и предполагается, что он не существует. Все, кто этого не учитывает, пытается решить открытую проблему.

Другая интересная информация, в основном неуязвимая:

Нижняя граница:

Легкая нижняя грань - это кантороподобное множество (числа 1..3 ^ n-1, не содержащие 1 в их тройном разложении) - его плотность равна n ^ (log_3 2) (около 0,631). Поэтому любая проверка, если набор не слишком велик, а затем проверка всех пар недостаточно, чтобы получить O (n log n). Вы должны исследовать последовательность умнее. Лучшая нижняя граница цитируется здесь - it n 1-c/(log (n)) ^ (1/2). Это означает, что набор канторов не оптимален.

Верхняя граница - мой старый алгоритм:

Известно, что при больших n подмножество {1,2,..., n}, не содержащее арифметической прогрессии, имеет не более n/(log n) ^ (1/20) элементов. Статья В тройках в арифметической прогрессии доказывает больше: набор не может содержать больше n * 2 28 * (журнал журнала n/log n) 1/2. Таким образом, вы можете проверить, достигнута ли эта граница, а если нет, наивно проверяйте пары. Это алгоритм O (n 2 * log log n/log n), который быстрее, чем O (n 2). К сожалению, "В тройках..." находится на Springer - но доступна первая страница, и доступна экспозиция Бена Грина здесь, стр. 28, теорема 24.

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

Ответ 3

Это не решение, но аналогичная мысль о том, что Olexiy думал

Я играл с созданием последовательностей с максимальным количеством единиц, и все они довольно интересны, я получил до 125 цифр и вот первые 3 числа, которые он нашел, пытаясь вставить как можно больше "1" битов

  • 11011000011011000000000000001101100001101100000000000000000000000000000000000000000110110000110110000000000000011011000011011
  • 10110100010110100000000000010110100010110100000000000000000000000000000000000000000101101000101101000000000000101101000101101
  • 10011001010011001000000000010011001010011001000000000000000000000000000000000000010011001010011001000000000010011001010011001

Обратите внимание, что все фракталы (не слишком удивительно, учитывая ограничения). Может быть, что-то в мышлении назад, возможно, если строка не является фрактальной с характеристикой, то она должна иметь повторяющийся шаблон?

Благодаря бета-версии для лучшего описания этих чисел.

Update: Увы, похоже, что шаблон распадается при запуске с достаточно большой начальной строкой, например: 10000000000001:

100000000000011
10000000000001101
100000000000011011
10000000000001101100001
100000000000011011000011
10000000000001101100001101
100000000000011011000011010000000001
100000000000011011000011010000000001001
1000000000000110110000110100000000010011
1000000000000110110000110100000000010011001
10000000000001101100001101000000000100110010000000001
10000000000001101100001101000000000100110010000000001000001
1000000000000110110000110100000000010011001000000000100000100000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011
1000000000000110110000110100000000010011001000000000100000100000000000001101
100000000000011011000011010000000001001100100000000010000010000000000000110100001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001000000000000000000000010010000010000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001000000000000000000000000000000000000110010000000000000000000000100100000100000011
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001000000110000000000001

Ответ 4

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

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

Ответ 5

Редакция: 2009-10-17 23:00

Я запустил это на больших количествах (например, строки из 20 миллионов), и теперь я считаю, что этот алгоритм не O (n logn). Несмотря на это, это довольно простая реализация и содержит ряд оптимизаций, которые заставляют ее работать очень быстро. Он оценивает все устройства двоичных строк 24 или меньше цифр менее чем за 25 секунд.

Я обновил код, чтобы включить наблюдение 0 <= L < M < U <= X-1 с сегодняшнего дня.


Оригинал

Это, по идее, похоже на еще один вопрос, на который я ответил. Этот код также рассмотрел три значения в серии и определил, удовлетворяет ли триплет условию. Вот код С#, который был адаптирован из этого:

using System;
using System.Collections.Generic;

namespace StackOverflow1560523
{
    class Program
    {
        public struct Pair<T>
        {
            public T Low, High;
        }
        static bool FindCandidate(int candidate, 
            List<int> arr, 
            List<int> pool, 
            Pair<int> pair, 
            ref int iterations)
        {
            int lower = pair.Low, upper = pair.High;
            while ((lower >= 0) && (upper < pool.Count))
            {
                int lowRange = candidate - arr[pool[lower]];
                int highRange = arr[pool[upper]] - candidate;
                iterations++;
                if (lowRange < highRange)
                    lower -= 1;
                else if (lowRange > highRange)
                    upper += 1;
                else
                    return true;
            }
            return false;
        }
        static List<int> BuildOnesArray(string s)
        {
            List<int> arr = new List<int>();
            for (int i = 0; i < s.Length; i++)
                if (s[i] == '1')
                    arr.Add(i);
            return arr;
        }
        static void BuildIndexes(List<int> arr, 
            ref List<int> even, ref List<int> odd, 
            ref List<Pair<int>> evenIndex, ref List<Pair<int>> oddIndex)
        {
            for (int i = 0; i < arr.Count; i++)
            {
                bool isEven = (arr[i] & 1) == 0;
                if (isEven)
                {
                    evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count+1});
                    oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count});
                    even.Add(i);
                }
                else
                {
                    oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count+1});
                    evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count});
                    odd.Add(i);
                }
            }
        }

        static int FindSpacedOnes(string s)
        {
            // List of indexes of 1s in the string
            List<int> arr = BuildOnesArray(s);
            //if (s.Length < 3)
            //    return 0;

            //  List of indexes to odd indexes in arr
            List<int> odd = new List<int>(), even = new List<int>();

            //  evenIndex has indexes into arr to bracket even numbers
            //  oddIndex has indexes into arr to bracket odd numbers
            List<Pair<int>> evenIndex = new List<Pair<int>>(), 
                oddIndex = new List<Pair<int>>(); 
            BuildIndexes(arr, 
                ref even, ref odd, 
                ref evenIndex, ref oddIndex);

            int iterations = 0;
            for (int i = 1; i < arr.Count-1; i++)
            {
                int target = arr[i];
                bool found = FindCandidate(target, arr, odd, oddIndex[i], ref iterations) || 
                    FindCandidate(target, arr, even, evenIndex[i], ref iterations);
                if (found)
                    return iterations;
            }
            return iterations;
        }
        static IEnumerable<string> PowerSet(int n)
        {
            for (long i = (1L << (n-1)); i < (1L << n); i++)
            {
                yield return Convert.ToString(i, 2).PadLeft(n, '0');
            }
        }
        static void Main(string[] args)
        {
            for (int i = 5; i < 64; i++)
            {
                int c = 0;
                string hardest_string = "";
                foreach (string s in PowerSet(i))
                {
                    int cost = find_spaced_ones(s);
                    if (cost > c)
                    {
                        hardest_string = s;
                        c = cost;
                        Console.Write("{0} {1} {2}\r", i, c, hardest_string);
                    }
                }
                Console.WriteLine("{0} {1} {2}", i, c, hardest_string);
            }
        }
    }
}

Основные отличия:

  • Исчерпывающий поиск решений
    Этот код генерирует набор мощности данных, чтобы найти самый сложный вход для решения этого алгоритма.
  • Все решения по сравнению с самыми трудными для решения
    Код для предыдущего вопроса генерировал все решения с использованием генератора питона. Этот код просто отображает самое сложное для каждой длины шаблона.
  • Алгоритм подсчета очков
    Этот код проверяет расстояние от среднего элемента до его левого и правого краев. Код python проверял, была ли сумма выше или ниже 0.
  • Конвергенция на кандидата
    Текущий код работает от середины к краю, чтобы найти кандидата. Код предыдущей проблемы работал от краев к середине. Это последнее изменение дает значительное улучшение производительности.
  • Использование четных и нечетных пулов
    На основании наблюдений в конце этой записи код ищет пары четных чисел пар нечетных чисел, чтобы найти L и U, сохраняя M фиксированным. Это уменьшает количество поисков по предварительной информации. Соответственно, код использует два уровня косвенности в основном цикле FindCandidate и требует два вызова FindCandidate для каждого среднего элемента: один раз для четных чисел и один раз для нечетных.

Общая идея заключается в том, чтобы работать с индексами, а не с необработанным представлением данных. Вычисление массива, где появляется 1, позволяет алгоритму работать во времени пропорционально числу 1 в данных, а не по времени, пропорциональному длине данных. Это стандартное преобразование: создайте структуру данных, которая позволяет быстрее работать, сохраняя эквивалентную проблему.

Результаты устарели: удалены.


Редактировать: 2009-10-16 18:48

По yx-данным, которым в других ответах дано некоторое доверие, как представителям жестких данных для вычисления, я получаю эти результаты... Я удалил их. Они устарели.

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


Редактировать: 2009-10-17 13:30

Дальнейшие наблюдения по этому вопросу.

Сначала преобразуем строку из 0 и 1 в массив индексов для каждой позиции 1. Скажем, длина этого массива A равна X. Тогда целью является найти

0 <= L < M < U <= X-1

такое, что

A[M] - A[L] = A[U] - A[M]

или

2*A[M] = A[L] + A[U]

Так как A [L] и A [U] суммируются с четным числом, они не могут быть (четными, нечетными) или (нечетными, четными). Поиск совпадения может быть улучшен путем разделения A [] на нечетные и равные пулы и поиска совпадений на A [M] в пулах нечетных и даже кандидатов по очереди.

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


Редактировать 2009-10-18 00:45

Еще одна оптимизация происходит со мной, в том же духе, что и разделение кандидатов на четные и нечетные. Поскольку три индекса должны быть добавлены к кратным 3 (a, a + x, a + 2x - mod 3 равно 0, независимо от a и x), вы можете отделить L, M и U от их значений mod 3

M  L  U
0  0  0
   1  2
   2  1
1  0  2
   1  1
   2  0
2  0  1
   1  0
   2  2

Фактически вы могли бы объединить это с четным/нечетным наблюдением и разделить их на свои значения mod 6:

M  L  U
0  0  0
   1  5
   2  4
   3  3
   4  2
   5  1

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

Ответ 6

Пока не удалось найти решение:(, но есть некоторые идеи.

Что делать, если мы начнем с обратной задачи: построим последовательность с максимальным числом 1 и БЕЗ равномерно распределенных трио. Если вы можете доказать, что максимальное число 1s равно o (n), вы можете улучшить свою оценку, итерации только через список из 1s.

Ответ 7

Это может помочь....

Эта проблема сводится к следующему:

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

Например, если задана последовательность [ 3, 5, 1, 3, 6, 5, 2, 2, 3, 5, 6, 4 ], мы найдем подпоследовательность [ 3, 6, 5, 2, 2] с префиксом [ 3, 6 ] с префиксом sum 9 и суффиксом [ 5, 2, 2 ] с суффиксом суммы 9.

Редукция выглядит следующим образом:

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

Например, учитывая последовательность [ 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 0 ], мы найдем редукцию [ 1, 3, 4]. Из этого сокращения вычисляем непрерывную подпоследовательность [ 1, 3, 4], префикс [ 1, 3] с суммой 4 и суффикс [ 4 ] с суммой 4.

Это сокращение может быть вычислено в O(n).

К сожалению, я не уверен, куда идти отсюда.

Ответ 8

Для простого типа проблемы (т.е. вы выполняете поиск по трем "1" только (т.е. ноль или более) "0" между ними), это довольно просто: вы могли бы просто разделить последовательность на каждом "1" и найдите две соседние подпоследовательности с одинаковой длиной (конечно, вторая подпоследовательность не является последней). Очевидно, это можно сделать в O (n) времени.

Для более сложной версии (т.е. вы ищете индекс я и зазор g > 0, такой, что s[i]==s[i+g]==s[i+2*g]=="1"), я не уверен, если существует решение O (n log n), так как возможно O (n²), обладающих этим свойством (подумайте о цепочке из всех, существует примерно n²/2 таких триплетов). Конечно, вы ищете только один из них, но в настоящее время я не знаю, как его найти...

Ответ 9

Интересный вопрос, но как только вы поймете, что фактическая картина между двумя "1" не имеет значения, алгоритм становится:

  • сканировать поиск "1"
  • начиная со следующего сканирования позиции для другого "1" (до конца массива минус расстояние от текущего первого "1" , или 3-й "1" будет за пределами границ)
  • если в позиции 2-го '1' плюс расстояние до первого 1 'найдено третье' 1 ', у нас есть равномерные пробелы.

В коде JTest fashion (обратите внимание, что этот код не является наиболее эффективным, и я добавил несколько println, чтобы узнать, что произойдет.)

import java.util.Random;

import junit.framework.TestCase;

public class AlgorithmTest extends TestCase {

 /**
  * Constructor for GetNumberTest.
  *
  * @param name The test name.
  */
 public AlgorithmTest(String name) {
  super(name);
 }

 /**
  * @see TestCase#setUp()
  */
 protected void setUp() throws Exception {
  super.setUp();
 }

 /**
  * @see TestCase#tearDown()
  */
 protected void tearDown() throws Exception {
  super.tearDown();
 }

 /**
  * Tests the algorithm.
  */
 public void testEvenlySpacedOnes() {

  assertFalse(isEvenlySpaced(1));
  assertFalse(isEvenlySpaced(0x058003));
  assertTrue(isEvenlySpaced(0x07001));
  assertTrue(isEvenlySpaced(0x01007));
  assertTrue(isEvenlySpaced(0x101010));

  // some fun tests
  Random random = new Random();

  isEvenlySpaced(random.nextLong());
  isEvenlySpaced(random.nextLong());
  isEvenlySpaced(random.nextLong());
 }

 /**
  * @param testBits
  */
 private boolean isEvenlySpaced(long testBits) {
  String testString = Long.toBinaryString(testBits);
  char[] ones = testString.toCharArray();
  final char ONE = '1';

  for (int n = 0; n < ones.length - 1; n++) {

   if (ONE == ones[n]) {
    for (int m = n + 1; m < ones.length - m + n; m++) {

     if (ONE == ones[m] && ONE == ones[m + m - n]) {
      System.out.println(" IS evenly spaced: " + testBits + '=' + testString);
      System.out.println("               at: " + n + ", " + m + ", " + (m + m - n));
      return true;
     }
    }
   }
  }

  System.out.println("NOT evenly spaced: " + testBits + '=' + testString);
  return false;
 }
}

Ответ 10

Я думал о подходе, который может работать.

Во-первых, при предварительной обработке вам нужно вставить все числа меньше половины вашего размера ввода ( n/3) в список.

С учетом строки: 0000010101000100 (обратите внимание, что данный пример действителен)

Вставьте все простые числа (и 1) от 1 до (16/2) в список: {1, 2, 3, 4, 5, 6, 7}

Затем разделим его пополам:

100000101 01000100

Продолжайте делать это до тех пор, пока не получите строки размером 1. Для всех строк размером один с 1 в них добавьте индекс строки в список возможностей; в противном случае возвращает -1 для отказа.

Вам также нужно будет вернуть список возможных расстояний между интервалами, связанных с каждым начальным индексом. (Начните с списка, который вы сделали выше, и удалите числа, когда вы идете). Здесь пустой список означает, что вы имеете дело только с одним 1, и поэтому в этот момент возможно расстояние между ними; в противном случае список включает пробелы, которые должны быть исключены.

Итак, продолжайте с приведенным выше примером:

1000 0101 0100 0100

10 00 01 01 01 00 01 00

1 0 0 0 0 1 0 1 0 1 0 0 0 1 0 0

На первом этапе сборки у нас теперь восемь наборов из двух. Во-первых, у нас есть возможность набора, но мы узнаем, что расстояние на 1 невозможно из-за того, что там есть другой нуль. Поэтому мы возвращаем 0 (для индекса) и {2,3,4,5,7} за то, что расстояние на 1 невозможно. Во втором случае мы ничего не имеем и возвращаем -1. В третьем мы имеем совпадение без пробелов, исключенных в индексе 5, поэтому возвращаем 5, {1,2,3,4,5,7}. В четвертой паре мы возвращаем 7, {1,2,3,4,5,7}. В пятом случае верните 9, {1,2,3,4,5,7}. В шестом возвратите -1. В седьмом случае возвращаем 13, {1,2,3,4,5,7}. В восьмом возврате -1. ​​

Объединяясь снова в четыре набора из четырех, мы имеем:

1000: Возврат (0, {4,5,6,7}) 0101: Возврат (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6,7}) 0100: Возврат (9, {3,4,5,6,7}) 0100: Возврат (13, {3,4,5,6,7})

Объединение в множества из восьми:

10000101: Return (0, {5,7}), (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5, 6,7}) 01000100: Return (9, {4,7}), (13, {3,4,5,6,7})

Объединение в набор из шестнадцати:

10000101 01000100

По мере продвижения мы продолжаем проверять все возможности до сих пор. До этого шага мы оставили материал, выходящий за пределы строки, но теперь мы можем проверить все возможности.

В принципе, мы проверяем первый 1 с интервалами 5 и 7 и находим, что они не выстраиваются в линию до 1. (Обратите внимание, что каждая проверка CONSTANT, а не линейное время). Затем мы проверяем второй (индекс 5) с интервалами 2, 3, 4, 5, 6 и 7 - или мы бы, но мы можем остановиться на 2 с которые фактически совпадают.

Уф! Это довольно длинный алгоритм.

Я не знаю 100%, если он O (n log n) из-за последнего шага, но все до него определенно O (n log n) насколько я могу судить. Я вернусь к этому позже и попытаюсь уточнить последний шаг.

EDIT: изменил мой ответ, чтобы отразить комментарий Welbog. Извините за ошибку. Позднее я напишу некоторый псевдокод, когда у меня появятся еще немного времени, чтобы расшифровать то, что я написал снова.; -)

Ответ 11

Я расскажу здесь свое грубое предположение и позволю тем, кто лучше вычисляет сложность, чтобы помочь мне в том, как мой алгоритм тарифицируется в O-notation wise

  • заданная двоичная строка 0000010101000100 (в качестве примера)
  • заголовок и хвост нулей → 00000 101010001 00
  • мы получаем 101010001 из предыдущего расчета
  • проверьте, является ли средний бит "одним", если он истинным, нашел допустимые три равномерно расположенных "одних" (только если число бит нечетно пронумеровано).
  • коррелированно, если оставшееся обрезанное количество бит равномерно пронумеровано, голова и хвост "один" не могут быть частью равномерно "одного",
  • мы используем 1010100001 в качестве примера (с дополнительным "нулем", чтобы стать даже нумерованным урожаем), в этом случае нам нужно снова обрезать, а затем → 10101 00001
  • мы получаем 10101 из предыдущего расчета и проверяем средний бит, и мы снова обнаружили бит с равномерным интервалом.

Я понятия не имею, как рассчитать сложность для этого, может ли кто-нибудь помочь?

edit: добавьте код, чтобы проиллюстрировать мою идею

edit2: попытался скомпилировать мой код и найти некоторые основные ошибки, исправленные

char *binaryStr = "0000010101000100";

int main() {
   int head, tail, pos;
   head = 0;
   tail = strlen(binaryStr)-1;
   if( (pos = find3even(head, tail)) >=0 )
      printf("found it at position %d\n", pos);
   return 0;
}

int find3even(int head, int tail) {
   int pos = 0;
   if(head >= tail) return -1;
   while(binaryStr[head] == '0') 
      if(head<tail) head++;
   while(binaryStr[tail] == '0') 
      if(head<tail) tail--;
   if(head >= tail) return -1;
   if( (tail-head)%2 == 0 && //true if odd numbered
       (binaryStr[head + (tail-head)/2] == '1') ) { 
         return head;
   }else {
      if( (pos = find3even(head, tail-1)) >=0 )
         return pos;
      if( (pos = find3even(head+1, tail)) >=0 )
         return pos;
   }
   return -1;
}

Ответ 12

Я придумал что-то вроде этого:

def IsSymetric(number):
    number = number.strip('0')

    if len(number) < 3:
        return False
    if len(number) % 2 == 0:
        return IsSymetric(number[1:]) or IsSymetric(number[0:len(number)-2])
    else:
        if number[len(number)//2] == '1':
            return True
        return IsSymetric(number[:(len(number)//2)]) or IsSymetric(number[len(number)//2+1:])
    return False

Это вдохновляет andycjw.

  • Обрезать нули.
  • Если даже тогда проверить две подстроки 0 - (len-2) (пропустить последний символ) и 1 - (len-1) (пропустить первый char)
  • Если даже не средний, чем средний char, мы имеем успех. Else разделите строку в midle без элемента midle и проверьте обе части.

Что касается сложности, это может быть O (nlogn), как в каждой рекурсии мы делим на два.

Надеюсь, что это поможет.

Ответ 13

Хорошо, я собираюсь сделать еще один удар по проблеме. Я думаю, что я могу доказать алгоритм O (n log (n)), который аналогичен тем, которые уже обсуждались, используя сбалансированное двоичное дерево для хранения расстояний между 1. Этот подход был вдохновлен наблюдением Юстиции о сокращении проблемы до списка расстояний между 1-м.

Можно ли отсканировать входную строку, чтобы построить сбалансированное двоичное дерево вокруг положения 1 так, чтобы каждый node сохранял положение 1, а каждое кромка помечено расстоянием до смежного 1 для каждого дочернего элемента node, Например:

10010001 gives the following tree

      3
     / \
  2 /   \ 3
   /     \
  0       7

Это можно сделать в O (n log (n)), так как для строки размера n каждая вставка принимает O (log (n)) в худшем случае.

Тогда проблема заключается в поиске дерева, чтобы узнать, есть ли в любом node путь от этого node через левый-ребенок, который имеет такое же расстояние, что и путь через правый ребенок. Это можно сделать рекурсивно на каждом поддереве. При слиянии двух поддеревьев в поиске мы должны сравнивать расстояния от путей в левом поддереве с расстояниями от путей справа. Поскольку количество путей в поддереве будет пропорционально log (n), а число узлов равно n, я считаю, что это можно сделать в O (n log (n)) времени.

Я что-то пропустил?

Ответ 14

Мне понравилась забавная проблема, поэтому я решил попробовать себя в этом.

Я делаю предположение, что 111000001 найдет первые 3 и будет успешным. По сути, число нулей, следующих за 1, важно, так как 0111000 совпадает с номером 111000 согласно вашему определению. Когда вы найдете два случая из 1, следующая 1 найденная завершает трилогию.

Здесь он находится в Python:

def find_three(bstring):
    print bstring
    dict = {}
    lastone = -1
    zerocount = 0
    for i in range(len(bstring)):
        if bstring[i] == '1':
            print i, ': 1'
            if lastone != -1:
                if(zerocount in dict):
                    dict[zerocount].append(lastone)
                    if len(dict[zerocount]) == 2:
                        dict[zerocount].append(i)
                        return True, dict
                else:
                    dict[zerocount] = [lastone]
            lastone = i
            zerocount = 0
        else:
            zerocount = zerocount + 1
    #this is really just book keeping, as we have failed at this point
    if lastone != -1:
        if(zerocount in dict):
            dict[zerocount].append(lastone)
        else:
            dict[zerocount] = [lastone]
    return False, dict

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

Ответ 15

Одна из причин проблемы - думать о факторах и смещении.

С переключением вы сравниваете строку единиц и нулей со сдвинутой версией самой. Затем вы берете соответствующие. Возьмите этот пример, сдвинутый на два:

1010101010
  1010101010
------------
001010101000

Полученный 1 (побитовый ANDed) должен представлять все те, которые равномерно распределены на два. Тот же пример сдвинут на три:

1010101010
   1010101010
-------------
0000000000000

В этом случае нет 1, которые равномерно распределены на три части.

Так что это говорит вам? Ну, вам нужно только проверить сдвиги, которые являются простыми числами. Например, вы говорите, что у вас есть два 1 из шести. Вам нужно было бы только проверить "две" смены и "три" смены (так как они делят шесть). Например:

10000010 
  10000010 (Shift by two)
    10000010
      10000010 (We have a match)

10000010
   10000010 (Shift by three)
      10000010 (We have a match)

Итак, единственные сдвиги, которые вам нужно проверить, - это 2,3,5,7,11,13 и т.д. До простого, ближайшего к квадратному корню размера строки цифр.

Почти решено?

Я думаю, что я ближе к решению. В основном:

  • Сканировать строку на 1. Для каждого 1 примечания это остаток после взятия модуля его положения. Модуль изменяется от 1 до половины размера строки. Это связано с тем, что наибольший размер разделения составляет половину строки. Это делается в O (n ^ 2). НО. Нужно проверить только простые модули, так что O (n ^ 2/log (n))
  • Сначала отсортируйте список модулей/остатков в порядке наибольшего модуля, это можно сделать в O (n * log (n)) времени.
  • Найдите три последовательных модуля/остатка, которые являются одинаковыми.
  • Как-то получить позицию из них!

Я думаю, что самый большой ключ к ответу заключается в том, что самыми быстрыми алгоритмами сортировки являются O (n * log (n)).

НЕПРАВИЛЬНО

Шаг 1 неверен, как указал коллега. Если у нас есть 1 в позиции 2,12 и 102. Тогда, принимая модуль 10, все они будут иметь одинаковые остатки и все же не будут равномерно разнесены друг от друга! К сожалению.

Ответ 16

Я предполагаю, что причина этого nlog (n) обусловлена ​​следующим:

  • Чтобы найти 1, который является началом триплета, вам нужно проверить (n-2) символы. Если вы не нашли его к этому моменту, вы не будете (символы n-1 и n не могут запустить триплет) (O (n))
  • Чтобы найти второй 1, который является частью триплета (начатый первым), вам нужно проверить m/2 (m = n-x, где x - смещение первых 1) символов. Это потому, что, если вы не нашли второй 1 к тому времени, когда вы находитесь на полпути от первого до конца, вы не будете..., так как третий 1 должен быть точно таким же, как и прошлое. (O (журнал (п)))
  • Это O (1), чтобы найти последний 1, так как вы знаете индекс, к которому он должен быть к тому времени, когда вы найдете первый и второй.

Итак, у вас есть n, log (n) и 1... O (nlogn)

Изменить: Ой, мой плохой. Мой мозг установил, что n/2 был logn... который, очевидно, не является (удвоение числа элементов по-прежнему удваивает количество итераций во внутреннем цикле). Это все еще при n ^ 2, не решая проблему. Ну, по крайней мере, мне нужно написать код:)


Реализация в Tcl

proc get-triplet {input} {
    for {set first 0} {$first < [string length $input]-2} {incr first} {
        if {[string index $input $first] != 1} {
            continue
        }
        set start [expr {$first + 1}]
        set end [expr {1+ $first + (([string length $input] - $first) /2)}]
        for {set second $start} {$second < $end} {incr second} {
            if {[string index $input $second] != 1} {
                continue
            }
            set last [expr {($second - $first) + $second}]
            if {[string index $input $last] == 1} {
                return [list $first $second $last]
            }
        }
    }
    return {}
}

get-triplet 10101      ;# 0 2 4
get-triplet 10111      ;# 0 2 4
get-triplet 11100000   ;# 0 1 2
get-triplet 0100100100 ;# 1 4 7

Ответ 17

Я думаю, что нашел способ решения проблемы, но я не могу построить формальное доказательство. Решение, которое я сделал, написано на Java, и оно использует счетчик 'n', чтобы подсчитать количество обращений к нему/массиву. Поэтому n должно быть меньше или равно stringLength * log (stringLength), если оно правильно. Я попробовал его для чисел от 0 до 2 ^ 22, и он работает.

Он начинается с итерации по входной строке и составления списка всех индексов, которые содержат один. Это просто O (n).

Затем из списка индексов он выбирает firstIndex и secondIndex, который больше первого. Эти два индекса должны содержать те, потому что они находятся в списке индексов. Оттуда можно вычислять третье значение. Если inputString [thirdIndex] равно 1, то он останавливается.

public static int testString(String input){
//n is the number of array/list accesses in the algorithm
int n=0;

//Put the indices of all the ones into a list, O(n)
ArrayList<Integer> ones = new ArrayList<Integer>();
for(int i=0;i<input.length();i++){
    if(input.charAt(i)=='1'){
        ones.add(i);
    }
}

//If less than three ones in list, just stop
if(ones.size()<3){
    return n;
}

int firstIndex, secondIndex, thirdIndex;
for(int x=0;x<ones.size()-2;x++){
    n++;
    firstIndex = ones.get(x);

    for(int y=x+1; y<ones.size()-1; y++){
        n++;
        secondIndex = ones.get(y);
        thirdIndex = secondIndex*2 - firstIndex;

        if(thirdIndex >= input.length()){
            break;
        }

        n++;
        if(input.charAt(thirdIndex) == '1'){
            //This case is satisfied if it has found three evenly spaced ones
            //System.out.println("This one => " + input);
            return n;
        }
    }
}

return n;

}

дополнительное примечание: счетчик n не увеличивается, когда он выполняет итерацию над входной строкой, чтобы построить список индексов. Эта операция - O (n), поэтому она никак не повлияет на сложность алгоритма.

Ответ 18

Предположение:

Просто неправильно, говоря о log (n) числе верхних пределов единиц

EDIT:

Теперь я обнаружил, что с использованием чисел Кантора (если правильно) плотность на множестве равна (2/3) ^ Log_3 (n) (какая странная функция), и я согласен, плотность log (n)/n является сильной.

Если это верхний предел, существует алгоритм, который решает эту проблему, по крайней мере, в O (n * (3/2) ^ (log (n)/log (3))) временная сложность и O ((3/2 ) ^ (log (n)/log (3))) пространственная сложность. (проверьте ответ "Правосудие" на algorhitm)

Это еще намного лучше, чем O (n ^ 2)

Эта функция ((3/2) ^ (log (n)/log (3))) на первый взгляд выглядит как n * log (n).

Как я получил эту формулу?

Аппликация числа канторов в строке.
Предположим, что длина строки равна 3 ^ p == n
На каждом этапе генерации строки Cantor вы сохраняете 2/3 числа из них. Примените это p раз.

Это означает (n * ((2/3) ^ p)) → (((3 ^ p)) * ((2/3) ^ p)) оставшиеся и после упрощения 2 ^ p. Это означает, что 2 ^ р в 3 ^ р струне → (3/2) ^ р. Замените p = log (n)/log (3) и получим ((3/2) ^ (log (n)/log (3)))

Ответ 19

При сканировании 1s добавьте свои позиции в список. При добавлении второго и последующего 1s сравните их с каждой позицией в списке до сих пор. Расстояние равно currentOne (в центре) - previousOne (слева). Правый бит - currentOne + интервал. Если это 1, конец.

Список единиц растет обратно пропорционально пространству между ними. Проще говоря, если у вас много 0s между 1s (как в худшем случае), ваш список известных 1s будет расти довольно медленно.

using System;
using System.Collections.Generic;

namespace spacedOnes
{
    class Program
    {
        static int[] _bits = new int[8] {128, 64, 32, 16, 8, 4, 2, 1};

        static void Main(string[] args)
        {
            var bytes = new byte[4];
            var r = new Random();
            r.NextBytes(bytes);
            foreach (var b in bytes) {
                Console.Write(getByteString(b));
            }
            Console.WriteLine();
            var bitCount = bytes.Length * 8;
            var done = false;
            var onePositions = new List<int>();
            for (var i = 0; i < bitCount; i++)
            {
                if (isOne(bytes, i)) {
                    if (onePositions.Count > 0) {
                        foreach (var knownOne in onePositions) {
                            var spacing = i - knownOne;
                            var k = i + spacing;
                            if (k < bitCount && isOne(bytes, k)) {
                                Console.WriteLine("^".PadLeft(knownOne + 1) + "^".PadLeft(spacing) + "^".PadLeft(spacing));
                                done = true;
                                break;
                            }
                        }
                    }
                    if (done) {
                        break;
                    }
                    onePositions.Add(i);
                }
            }
            Console.ReadKey();
        }

        static String getByteString(byte b) {
            var s = new char[8];
            for (var i=0; i<s.Length; i++) {
                s[i] = ((b & _bits[i]) > 0 ? '1' : '0');
            }
            return new String(s);
        }

        static bool isOne(byte[] bytes, int i)
        {
            var byteIndex = i / 8;
            var bitIndex = i % 8;
            return (bytes[byteIndex] & _bits[bitIndex]) > 0;
        }
    }
}

Ответ 20

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

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

  • Обрезать передние и конечные нули.
  • Сканировать строку, ищущую 1-е.
  • Когда найдено 1:
    • Предположим, что это средний 1 решения.
    • Для каждого предшествующего 1 используйте его сохраненную позицию для вычисления ожидаемой позиции окончательного 1.
    • Если вычисленная позиция находится после окончания строки, она не может быть частью решения, поэтому оставьте позицию из списка кандидатов.
    • Проверьте решение.
  • Если решение не было найдено, добавьте текущий список 1 в список кандидатов.
  • Повторяйте, пока не будет найдено больше 1.

Теперь рассмотрим строки строк ввода, такие как следующее, которое не будет иметь решения:

101
101001
1010010001
101001000100001
101001000100001000001

В общем, это конкатенация k строк формы j 0, за которой следует 1 для j от нуля до k-1.

k=2  101
k=3  101001
k=4  1010010001
k=5  101001000100001
k=6  101001000100001000001

Обратите внимание, что длины подстрок равны 1, 2, 3 и т.д. Таким образом, размер задачи n имеет подстроки длиной от 1 до k такие, что n = k (k + 1)/2.

k=2  n= 3  101
k=3  n= 6  101001
k=4  n=10  1010010001
k=5  n=15  101001000100001
k=6  n=21  101001000100001000001

Заметим, что k также отслеживает число 1, которое мы должны рассмотреть. Помните, что каждый раз, когда мы видим 1, нам нужно рассмотреть все увиденные до сих пор. Поэтому, когда мы видим второй 1, мы рассматриваем только первое, когда видим третий 1, мы пересматриваем первые два, когда видим четвертый 1, нам нужно пересмотреть первые три и так далее. К концу алгоритма мы рассмотрели k (k-1)/2 пары из 1. Назовите это p.

k=2  n= 3  p= 1  101
k=3  n= 6  p= 3  101001
k=4  n=10  p= 6  1010010001
k=5  n=15  p=10  101001000100001
k=6  n=21  p=15  101001000100001000001

Отношение между n и p состоит в том, что n = p + k.

Процесс прохождения строки принимает время O (n). Каждый раз, когда встречается 1, выполняется максимальное (k-1) сравнение. Так как n = k (k + 1)/2, n > k ** 2, то sqrt (n) > k. Это дает нам O (n sqrt (n)) или O (n ** 3/2). Обратите внимание, однако, что это может быть не очень плотная связь, потому что количество сравнений идет от 1 до максимума k, это не k все время. Но я не уверен, как объяснить это в математике.

Это еще не O (n log (n)). Кроме того, я не могу доказать, что эти материалы являются наихудшими случаями, хотя я подозреваю, что они есть. Я думаю, что более плотная упаковка 1 на фронт приводит к еще более разреженной упаковке в конце.

Так как кто-то все еще может найти это полезным, вот мой код для этого решения в Perl:

#!/usr/bin/perl

# read input as first argument
my $s = $ARGV[0];

# validate the input
$s =~ /^[01]+$/ or die "invalid input string\n";

# strip leading and trailing 0's
$s =~ s/^0+//;
$s =~ s/0+$//;

# prime the position list with the first '1' at position 0
my @p = (0);

# start at position 1, which is the second character
my $i = 1;

print "the string is $s\n\n";

while ($i < length($s)) {
   if (substr($s, $i, 1) eq '1') {
      print "found '1' at position $i\n";
      my @t = ();
      # assuming this is the middle '1', go through the positions
      # of all the prior '1 and check whether there another '1'
      # in the correct position after this '1' to make a solution
      while (scalar @p) {
         # $p is the position of the prior '1'
         my $p = shift @p;
         # $j is the corresponding position for the following '1'
         my $j = 2 * $i - $p;
         # if $j is off the end of the string then we don't need to
         # check $p anymore
         next if ($j >= length($s));
         print "checking positions $p, $i, $j\n";
         if (substr($s, $j, 1) eq '1') {
            print "\nsolution found at positions $p, $i, $j\n";
            exit 0;
         }
         # if $j isn't off the end of the string, keep $p for next time
         push @t, $p;
      }
      @p = @t;
      # add this '1' to the list of '1' positions
      push @p, $i;
   }
   $i++;
}

print "\nno solution found\n";

Ответ 21

Я думал, что добавлю один комментарий, прежде чем отправлять 22-е наивное решение проблемы. Для наивного решения нам не нужно указывать, что число 1 в строке не более O (log (n)), а скорее не больше O (sqrt (n * log (n)).

Solver:

def solve(Str):
    indexes=[]
    #O(n) setup
    for i in range(len(Str)):
        if Str[i]=='1':
            indexes.append(i)

    #O((number of 1's)^2) processing
    for i in range(len(indexes)):
        for j in range(i+1, len(indexes)):
                            indexDiff = indexes[j] - indexes[i]
            k=indexes[j] + indexDiff
            if k<len(Str) and Str[k]=='1':
                return True
    return False

Это в основном честный бит, похожий на идею и реализацию flybywire, хотя и смотрит вперед, а не назад.

Greedy String Builder:

#assumes final char hasn't been added, and would be a 1 
def lastCharMakesSolvable(Str):
    endIndex=len(Str)
    j=endIndex-1
    while j-(endIndex-j) >= 0:
        k=j-(endIndex-j)
        if k >= 0 and Str[k]=='1' and Str[j]=='1':
            return True
        j=j-1
    return False



def expandString(StartString=''):
    if lastCharMakesSolvable(StartString):
        return StartString + '0'
    return StartString + '1'

n=1
BaseStr=""
lastCount=0
while n<1000000:
    BaseStr=expandString(BaseStr)
    count=BaseStr.count('1')
    if count != lastCount:
        print(len(BaseStr), count)
    lastCount=count
    n=n+1

(В моей защите я все еще нахожусь в стадии понимания "python" )

Кроме того, потенциально полезный вывод из жадного построения строк, есть довольно последовательный прыжок после удара 2-го числа в количестве 1..., который я не хотел ждать, чтобы засвидетельствовать, что он напал на 2096.

strlength   # of 1's
    1    1
    2    2
    4    3
    5    4
   10    5
   14    8
   28    9
   41    16
   82    17
  122    32
  244    33
  365    64
  730    65
 1094    128
 2188    129
 3281    256
 6562    257
 9842    512
19684    513
29525    1024

Ответ 22

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

  • Учитывая фиксированное число пробелов k и строку S, поиск k-spaced-triplet принимает O(n). Мы просто проверяем для каждого 0<=i<=(n-2k), если S[i]==S[i+k]==S[i+2k]. Тест принимает O(1), и мы делаем это n-k раз, когда k является константой, поэтому требуется O(n-k)=O(n).

  • Предположим, что существует обратная пропорция между числом 1 и максимальными пробелами, которые нам нужно искать. То есть, если существует много 1, то должен быть триплет, и он должен быть довольно плотным; Если всего несколько 1, Триплет (если он есть) может быть довольно разреженным. Другими словами, я могу доказать, что, если у меня достаточно 1 's, такой триплет должен существовать - и чем больше 1, тем более должен быть найден более плотный триплет. Это можно объяснить с помощью Принцип Pigeonhole - Надеюсь подробнее рассказать об этом позже.

  • Скажите, что есть верхняя граница k о возможном числе пробелов, которые я должен искать. Теперь для каждого 1, расположенного в S[i], нам нужно проверить 1 в S[i-1] и S[i+1], S[i-2] и S[i+2],... S[i-k] и S[i+k]. Это берет O((k^2-k)/2)=O(k^2) для каждого 1 в S - из-за Формула суммирования по формуле Гаусса. Обратите внимание, что это отличается от раздела 1 - у меня есть k как верхняя граница для числа пробелов, а не как постоянное пространство.

Нам нужно доказать O(n*log(n)). То есть нам нужно показать, что k*(number of 1's) пропорционально log(n).

Если мы сможем это сделать, алгоритм тривиален - для каждого 1 в S, индекс которого i, просто найдите 1 с каждой стороны до расстояния k. Если два найденных на одном расстоянии, верните i и k. Опять же, сложная часть будет находить k и доказывать правильность.

Я был бы очень признателен за ваши комментарии здесь - я пытался найти связь между k и числом 1 на моей доске, до сих пор без успеха.

Ответ 23

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

Изменить: это не n * log (n)

КОД PSEUDO:

foreach character in the string
  if the character equals 1 {         
     if length cache > 0 { //we can skip the first one
        foreach location in the cache { //last in first out kind of order
           if ((currentlocation + (currentlocation - location)) < length string)
              if (string[(currentlocation + (currentlocation - location))] equals 1)
                 return found evenly spaced string
           else
              break;
        }
     }
     remember the location of this character in a some sort of cache.
  }

return didn't find evenly spaced string

Код С#:

public static Boolean FindThreeEvenlySpacedOnes(String str) {
    List<int> cache = new List<int>();

    for (var x = 0; x < str.Length; x++) {
        if (str[x] == '1') {
            if (cache.Count > 0) {
                for (var i = cache.Count - 1; i > 0; i--) {
                    if ((x + (x - cache[i])) >= str.Length)
                        break;

                    if (str[(x + (x - cache[i]))] == '1')
                        return true;                            
                }
            }
            cache.Add(x);                    
        }
    }

    return false;
}

Как это работает:

iteration 1:
x
|
101101001
// the location of this 1 is stored in the cache

iteration 2:
 x
 | 
101101001

iteration 3:
a x b 
| | | 
101101001
//we retrieve location a out of the cache and then based on a 
//we calculate b and check if te string contains a 1 on location b

//and of course we store x in the cache because it a 1

iteration 4:
  axb  
  |||  
101101001

a  x  b  
|  |  |  
101101001


iteration 5:
    x  
    |  
101101001

iteration 6:
   a x b 
   | | | 
101101001

  a  x  b 
  |  |  | 
101101001
//return found evenly spaced string

Ответ 24

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

Алгоритм в основном работает в четыре этапа:

Этап 1: для каждого бита в вашем исходном номере выясните, насколько далеко они расположены, но учитывайте только одно направление. (Я рассмотрел все биты в направлении наименее значимого бита.)

Этап 2: измените порядок бит на входе;

Этап 3: повторите шаг 1 на обратном входе.

Этап 4: Сравните результаты с этапа 1 и 3-й стадии. Если какие-либо биты находятся на одинаковом расстоянии выше И ниже, мы должны получить удар.

Имейте в виду, что ни один шаг в этом алгоритме не занимает больше времени, чем O (n). ^ _ ^

В качестве дополнительного преимущества этот алгоритм найдет ВСЕ одинаково разнесенные из КАЖДОГО числа. Так, например, если вы получаете результат "0x0005", тогда есть равные интервалы на BOTH 1 и 3 единицы.

Я действительно не пытался оптимизировать код ниже, но это компилируемый код С#, который, кажется, работает.

using System;

namespace ThreeNumbers
{
    class Program
    {
        const int uint32Length = 32;

        static void Main(string[] args)
        {
            Console.Write("Please enter your integer: ");
            uint input = UInt32.Parse(Console.ReadLine());

            uint[] distancesLower = Distances(input);
            uint[] distancesHigher = Distances(Reverse(input));

            PrintHits(input, distancesLower, distancesHigher);
        }

        /// <summary>
        /// Returns an array showing how far the ones away from each bit in the input.  Only 
        /// considers ones at lower signifcant bits.  Index 0 represents the least significant bit 
        /// in the input.  Index 1 represents the second least significant bit in the input and so 
        /// on.  If a one is 3 away from the bit in question, then the third least significant bit 
        /// of the value will be sit.
        /// 
        /// As programed this algorithm needs: O(n) time, and O(n*log(n)) space.  
        /// (Where n is the number of bits in the input.)
        /// </summary>
        public static uint[] Distances(uint input)
        {
            uint[] distanceToOnes = new uint[uint32Length];
            uint result = 0;

            //Sets how far each bit is from other ones. Going in the direction of LSB to MSB
            for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex)
            {
                distanceToOnes[arrayIndex] = result;
                result <<= 1;

                if ((input & bitIndex) != 0)
                {
                    result |= 1;
                }
            }

            return distanceToOnes;
        }

        /// <summary>
        /// Reverses the bits in the input.
        /// 
        /// As programmed this algorithm needs O(n) time and O(n) space.  
        /// (Where n is the number of bits in the input.)
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static uint Reverse(uint input)
        {
            uint reversedInput = 0;
            for (uint bitIndex = 1; bitIndex != 0; bitIndex <<= 1)
            {
                reversedInput <<= 1;
                reversedInput |= (uint)((input & bitIndex) != 0 ? 1 : 0);
            }

            return reversedInput;
        }

        /// <summary>
        /// Goes through each bit in the input, to check if there are any bits equally far away in 
        /// the distancesLower and distancesHigher
        /// </summary>
        public static void PrintHits(uint input, uint[] distancesLower, uint[] distancesHigher)
        {
            const int offset = uint32Length - 1;

            for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex)
            {
                //hits checks if any bits are equally spaced away from our current value
                bool isBitSet = (input & bitIndex) != 0;
                uint hits = distancesLower[arrayIndex] & distancesHigher[offset - arrayIndex];

                if (isBitSet && (hits != 0))
                {
                    Console.WriteLine(String.Format("The {0}-th LSB has hits 0x{1:x4} away", arrayIndex + 1, hits));
                }
            }
        }
    }
}

Кто-то, вероятно, прокомментирует, что для любого достаточно большого числа побитовые операции не могут быть выполнены в O (1). Вы были бы правы. Однако я бы предположил, что любое решение, которое использует сложение, вычитание, умножение или деление (что не может быть сделано путем переключения), также будет иметь эту проблему.

Ответ 25

Очевидно, нам нужно, по крайней мере, проверять пучки триплетов одновременно, поэтому нам нужно как-то сжать чеки. У меня есть алгоритм-кандидат, но анализ временной сложности за пределами моей возможности * порогового значения времени.

Создайте дерево, в котором каждый node имеет трех детей, и каждый node содержит общее число 1 на его листах. Создайте связанный список по 1, также. Присвойте каждому node допустимую стоимость, пропорциональную диапазону, который он охватывает. Пока время, затрачиваемое на каждый node, находится в пределах бюджета, у нас будет алгоритм O (n lg n).

-

Начните с корня. Если квадрат общего числа 1 ниже его меньше его допустимой стоимости, примените наивный алгоритм. В противном случае рекрутируйте своих детей.

Теперь мы либо возвращаемся в рамках бюджета, либо знаем, что нет никаких правильных триплетов, полностью содержащихся в одном из детей. Поэтому мы должны проверить триплеты inter node.

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

-

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

Время выполнения алгоритма вовсе не очевидно. Он опирается на нетривиальные свойства последовательности. Если 1 действительно разрежен, то наивный алгоритм будет работать в рамках бюджета. Если 1 плотно, то сразу следует найти совпадение. Но если плотность "справедлива" (например, около ~ n ^ 0,63, которую вы можете достичь, установив все биты в позиции без цифры "2" в базе 3), я не знаю, будет ли она работать. Вам нужно будет доказать, что эффект расщепления достаточно прочен.

Ответ 26

Здесь нет теоретического ответа, но я написал краткую программу Java для изучения поведения времени выполнения как функции от k и n, где n - общая длина бит, а k - число 1. Я с несколькими респондентами, которые говорят, что "обычный" алгоритм, который проверяет все пары позиций битов и ищет 3-й бит, даже если он потребует O (k ^ 2) в худшем случае, в реальность, потому что в худшем случае нужны разреженные бистроны, O (n ln n).

В любом случае здесь программа, ниже. Это программа стиля Монте-Карло, которая запускает большое количество испытаний NTRIALS для постоянного n и случайным образом генерирует биты для диапазона k-значений с использованием процессов Bernoulli с ограничением одной плотности, ограниченным пределами, которые могут быть указаны, и записывает время выполнения найти или не найти триплет равномерно разнесенных, время, измеренное по этапам НЕ в процессорном времени. Я запустил его для n = 64, 256, 1024, 4096, 16384 * (все еще работает), сначала тестовый прогон с 500 000 проб, чтобы узнать, какие значения k принимают самое длинное время работы, а затем еще один тест с 5000000 пробными сужениями - чтобы увидеть, как выглядят эти значения. Длительное время работы происходит с очень низкой плотностью (например, для n = 4096 пики рабочего времени находятся в диапазоне k = 16-64, с нежным пиком для средней продолжительности работы при 4212 шагах @k = 31, максимальное время выполнения достигло максимума в 5101 шаги @k = 58). Похоже, что для достижения наихудшего варианта O (k ^ 2) для N будет наибольшим значением N, чтобы стать больше, чем шаг O (n), где вы сканируете битструю, чтобы найти 1 индекс позиции.

package com.example.math;

import java.io.PrintStream;
import java.util.BitSet;
import java.util.Random;

public class EvenlySpacedOnesTest {
    static public class StatisticalSummary
    {
        private int n=0;
        private double min=Double.POSITIVE_INFINITY;
        private double max=Double.NEGATIVE_INFINITY;
        private double mean=0;
        private double S=0;

        public StatisticalSummary() {}
        public void add(double x) {
            min = Math.min(min, x);
            max = Math.max(max, x);
            ++n;
            double newMean = mean + (x-mean)/n;
            S += (x-newMean)*(x-mean);
            // this algorithm for mean,std dev based on Knuth TAOCP vol 2
            mean = newMean;
        }
        public double getMax() { return (n>0)?max:Double.NaN; }
        public double getMin() { return (n>0)?min:Double.NaN; }
        public int getCount() { return n; }
        public double getMean() { return (n>0)?mean:Double.NaN; }
        public double getStdDev() { return (n>0)?Math.sqrt(S/n):Double.NaN; } 
        // some may quibble and use n-1 for sample std dev vs population std dev    
        public static void printOut(PrintStream ps, StatisticalSummary[] statistics) {
            for (int i = 0; i < statistics.length; ++i)
            {
                StatisticalSummary summary = statistics[i];
                ps.printf("%d\t%d\t%.0f\t%.0f\t%.5f\t%.5f\n",
                        i,
                        summary.getCount(),
                        summary.getMin(),
                        summary.getMax(),
                        summary.getMean(),
                        summary.getStdDev());
            }
        }
    }

    public interface RandomBernoulliProcess // see http://en.wikipedia.org/wiki/Bernoulli_process
    {
        public void setProbability(double d);
        public boolean getNextBoolean();
    }

    static public class Bernoulli implements RandomBernoulliProcess
    {
        final private Random r = new Random();
        private double p = 0.5;
        public boolean getNextBoolean() { return r.nextDouble() < p; }
        public void setProbability(double d) { p = d; }
    }   
    static public class TestResult {
        final public int k;
        final public int nsteps;
        public TestResult(int k, int nsteps) { this.k=k; this.nsteps=nsteps; } 
    }

    ////////////
    final private int n;
    final private int ntrials;
    final private double pmin;
    final private double pmax;
    final private Random random = new Random();
    final private Bernoulli bernoulli = new Bernoulli();
    final private BitSet bits;
    public EvenlySpacedOnesTest(int n, int ntrials, double pmin, double pmax) {
        this.n=n; this.ntrials=ntrials; this.pmin=pmin; this.pmax=pmax;
        this.bits = new BitSet(n);
    }

    /*
     * generate random bit string
     */
    private int generateBits()
    {
        int k = 0; // # of 1's
        for (int i = 0; i < n; ++i)
        {
            boolean b = bernoulli.getNextBoolean();
            this.bits.set(i, b);
            if (b) ++k;
        }
        return k;
    }

    private int findEvenlySpacedOnes(int k, int[] pos) 
    {
        int[] bitPosition = new int[k];
        for (int i = 0, j = 0; i < n; ++i)
        {
            if (this.bits.get(i))
            {
                bitPosition[j++] = i;
            }
        }
        int nsteps = n; // first, it takes N operations to find the bit positions.
        boolean found = false;
        if (k >= 3) // don't bother doing anything if there are less than 3 ones. :(
        {       
            int lastBitSetPosition = bitPosition[k-1];
            for (int j1 = 0; !found && j1 < k; ++j1)
            {
                pos[0] = bitPosition[j1];
                for (int j2 = j1+1; !found && j2 < k; ++j2)
                {
                    pos[1] = bitPosition[j2];

                    ++nsteps;
                    pos[2] = 2*pos[1]-pos[0];
                    // calculate 3rd bit index that might be set;
                    // the other two indices point to bits that are set
                    if (pos[2] > lastBitSetPosition)
                        break;
                    // loop inner loop until we go out of bounds

                    found = this.bits.get(pos[2]);
                    // we're done if we find a third 1!
                }
            }
        }
        if (!found)
            pos[0]=-1;
        return nsteps;
    }

    /*
     * run an algorithm that finds evenly spaced ones and returns # of steps.
     */
    public TestResult run()
    {
        bernoulli.setProbability(pmin + (pmax-pmin)*random.nextDouble());
        // probability of bernoulli process is randomly distributed between pmin and pmax

        // generate bit string.
        int k = generateBits();
        int[] pos = new int[3];
        int nsteps = findEvenlySpacedOnes(k, pos);
        return new TestResult(k, nsteps); 
    }

    public static void main(String[] args)
    {
        int n;
        int ntrials;
        double pmin = 0, pmax = 1;
        try {
            n = Integer.parseInt(args[0]);
            ntrials = Integer.parseInt(args[1]);
            if (args.length >= 3)
                pmin = Double.parseDouble(args[2]);
            if (args.length >= 4)
                pmax = Double.parseDouble(args[3]);
        }
        catch (Exception e)
        {
            System.out.println("usage: EvenlySpacedOnesTest N NTRIALS [pmin [pmax]]");
            System.exit(0);
            return; // make the compiler happy
        }

        final StatisticalSummary[] statistics;
        statistics=new StatisticalSummary[n+1];
        for (int i = 0; i <= n; ++i)
        {
            statistics[i] = new StatisticalSummary();
        }

        EvenlySpacedOnesTest test = new EvenlySpacedOnesTest(n, ntrials, pmin, pmax);
        int printInterval=100000;
        int nextPrint = printInterval;
        for (int i = 0; i < ntrials; ++i)
        {
            TestResult result = test.run();
            statistics[result.k].add(result.nsteps);
            if (i == nextPrint)
            {
                System.err.println(i);
                nextPrint += printInterval;
            }
        }
        StatisticalSummary.printOut(System.out, statistics);
    }
}

Ответ 27

# <algorithm>
def contains_evenly_spaced?(input)
  return false if input.size < 3
  one_indices = []
  input.each_with_index do |digit, index|
    next if digit == 0
    one_indices << index
  end
  return false if one_indices.size < 3
  previous_indexes = []
  one_indices.each do |index|
    if !previous_indexes.empty?
      previous_indexes.each do |previous_index|
        multiple = index - previous_index
        success_index = index + multiple
        return true if input[success_index] == 1
      end
    end
    previous_indexes << index
  end
  return false
end
# </algorithm>

def parse_input(input)
  input.chars.map { |c| c.to_i }
end

У меня возникают проблемы с наихудшими сценариями с миллионами цифр. Fuzzing от /dev/urandom по существу дает вам O (n), но я знаю, что худший случай хуже этого. Я просто не могу сказать, насколько хуже. Для малых n тривиально найти входы около 3*n*log(n), но удивительно трудно отличить их от некоторого другого порядка роста для этой конкретной проблемы.

Может ли кто-нибудь, кто работал на входы наихудшего случая, генерировать строку с длиной больше, чем скажем, сто тысяч?

Ответ 29

Может ли это быть решением? Я не уверен, что это O (nlogn), но, на мой взгляд, это лучше, чем O (n²), потому что единственным способом не найти тройку будет распределение простых чисел.

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

#include <iostream>

#include <string>

int findIt(std::string toCheck) {
    for (int i=0; i<toCheck.length(); i++) {
        if (toCheck[i]=='1') {
            std::cout << i << ": " << toCheck[i];
            for (int j = i+1; j<toCheck.length(); j++) {
                if (toCheck[j]=='1' && toCheck[(i+2*(j-i))] == '1') {
                    std::cout << ", " << j << ":" << toCheck[j] << ", " << (i+2*(j-i)) << ":" << toCheck[(i+2*(j-i))] << "    found" << std::endl;
                    return 0;
                }
            }
        }
    }
    return -1;
}

int main (int agrc, char* args[]) {
    std::string toCheck("1001011");
    findIt(toCheck);
    std::cin.get();
    return 0;
}

Ответ 30

Я думаю, что этот алгоритм имеет сложность O (n log n) (С++, DevStudio 2k5). Теперь я не знаю подробностей о том, как анализировать алгоритм, чтобы определить его сложность, поэтому я добавил некоторую метрическую информацию для сбора кода. Код подсчитывает количество тестов, выполненных в последовательности 1 и 0 для любого заданного ввода (надеюсь, я не сделал шаров алгоритма). Мы можем сравнить фактическое количество тестов с значением O и посмотреть, есть ли корреляция.

#include <iostream>
using namespace std;

bool HasEvenBits (string &sequence, int &num_compares)
{
  bool
    has_even_bits = false;

  num_compares = 0;

  for (unsigned i = 1 ; i <= (sequence.length () - 1) / 2 ; ++i)
  {
    for (unsigned j = 0 ; j < sequence.length () - 2 * i ; ++j)
    {
      ++num_compares;
      if (sequence [j] == '1' && sequence [j + i] == '1' && sequence [j + i * 2] == '1')
      {
        has_even_bits = true;
        // we could 'break' here, but I want to know the worst case scenario so keep going to the end
      }
    }
  }

  return has_even_bits;
}

int main ()
{
  int
    count;

  string
    input = "111";

  for (int i = 3 ; i < 32 ; ++i)
  {
    HasEvenBits (input, count);
    cout << i << ", " << count << endl;
    input += "0";
  }
}

Эта программа выводит количество тестов для каждой строки длиной до 32 символов. Здесь результаты:

 n  Tests  n log (n)
=====================
 3     1     1.43
 4     2     2.41
 5     4     3.49
 6     6     4.67
 7     9     5.92
 8    12     7.22
 9    16     8.59
10    20    10.00
11    25    11.46
12    30    12.95
13    36    14.48
14    42    16.05
15    49    17.64
16    56    19.27
17    64    20.92
18    72    22.59
19    81    24.30
20    90    26.02
21   100    27.77
22   110    29.53
23   121    31.32
24   132    33.13
25   144    34.95
26   156    36.79
27   169    38.65
28   182    40.52
29   196    42.41
30   210    44.31
31   225    46.23

Я добавил значения "n log n". Постройте их, используя свой графический инструмент, чтобы увидеть корреляцию между этими двумя результатами. Этот анализ распространяется на все значения n? Я не знаю.