#algorithm
Вопрос:
Можно ли изменить алгоритм обнаружения циклов Флойда, чтобы найти все числа, имеющие повторение в массиве? Я не могу изменить массив.
Учитывая массив для размера n
с целыми 1,...n-1
числами , по принципу ячейки, существует повторяющееся целое число. — *
Реализация алгоритма обнаружения циклов Флойда успешно помогает найти дубликат во O(n)
времени и O(1)
пространстве.
Я хочу разобраться со случаем, когда существует более одного дубликата. Это мои мысли:
- Каждое повторение числа приводит к циклу (при условии *). Но никакие два разных целых числа, имеющих повторение, не могут находиться в одном и том же цикле. Поэтому мы должны найти все такие циклы.
- Каждый путь цикла будет иметь форму
--------o
, в которой---------
та часть, с которой мы начинаем два указателя для нашего алгоритма определения цикла,o
является циклом. - Нам нужно найти точки входа в цикл, поэтому мы держим один указатель на первом
-
месте в нашем цикле, а другой-там, где указатели встретились изначально, перемещение их вместе с одинаковой скоростью приведет к тому, что они встретятся в точке входа цикла. - Поэтому, если у нас есть первый
-
из нашего «Циклического пути», мы можем найти дубликат, который привел к этому циклу.
Как мне найти первую -
оптимальную для каждого велосипедного пути? Хранение посещенных узлов просто приведет к потере этой сложности пространства O(1), и было бы относительно лучше вместо этого просто использовать хэш-таблицу.
Мне нужно было бы сохранить некоторую информацию, чтобы сообщить, что велосипедный путь уже посещен, поэтому места не может быть O(1)
в соответствии с моим пониманием, но как мне в любом случае минимизировать пространство.
Возможен ли вообще такой подход, не ошибаюсь ли я где-то в приведенных выше идеях? Какой другой метод был бы осуществим, используя обнаружение циклов и оптимальное нахождение нескольких дубликатов?
Обновление: Если описанный выше подход осуществим, проблема сводится к выходу из цикла и нахождению не посещенного цикла. Я могу узнать, посещается ли цикл, сохранив номер, в котором наши указатели встречаются изначально. Так что технически занимаем O(k)
место для k
дубликатов. Поиск другого цикла можно было бы выполнить с помощью рандомизированного запуска и проверки, посещен ли цикл или нет. Но по мере приближения числа найденных дубликатов k
рандомизированный запуск будет замедляться и может никогда не завершиться. Кроме того, существует ли какой-либо разумный способ выбрать индекс для рандомизированного подхода, чтобы он гарантировал завершение?
Комментарии:
1. Я не думаю, что вам нужно хранить первый
-
запуск в O(N). Просто запустите модифицированную версию алгоритма, где каждый посещенный узел помечен (реализация зависит от вас) таким образом, чтобы можно было восстановить его исходное значение. После выполнения первой итерации перейдите к следующему узлу,который не был отмечен, запустите модифицированный алгоритм; не забудьте сопоставить исходное значение для отмеченных узлов, чтобы определить соседей. Он все равно будет работать в O(2N) = O(N) времени (почему?) и явно использует O(1) пространство. После достижения конца снимите пометки со всех отмеченных узлов, чтобы восстановить исходный массив.2. @wLui155 Я пропустил требование, чтобы массив не мог быть изменен, если вы предлагаете, чтобы я связывал логические значения с каждым индексом массива. Поэтому я не могу отметить это. Создание другого массива для хранения логических значений mark/unmark потребует дополнительной памяти.
3. Хм, это немного усложняет ситуацию — это то, что массив читается только для начала? Метод, о котором я думал, восстановит его, но если это невозможно, то мой предложенный подход не сработает.
4. С другой стороны, даже если бы я мог изменить массив, как я буду отмечать их, сохраняя дальнейшие проходы алгоритма в пределах одного и того же циклического пути?
5. Чтобы отменить узел в индексе
i
вашего массиваA
, просто назначьте-A[i]
A[i] if A[i] > 0
ему . Хорошая вещь в отрицании заключается в том, что функция абсолютного значения даст исходное значение (которое гарантированно было положительным). Таким образом, вы, по сути, кодируете два состояния внутри одного узла в постоянной памяти — независимо от того, посещен он или нет (положительный/отрицательный) и сосед (абсолютное значение сохраненного значения). Например, если у нас есть входной массив[4, 2, 1, 2, 1]
и он проиндексирован на 0, вы бы запустили алгоритм с индексом 0 и в итоге получили[-4, -2, -1, 2, -1]
, где1
находится дубликат.