Разбираем удаление дубликатов из отсортированного массива, где каждый элемент может встречаться не более двух раз. Решение с двумя указателями и O(1) памяти.
На LeetCode есть задача Remove Duplicates from Sorted Array II — среднего уровня, но она может поставить в тупик даже опытных разработчиков. Многие пытаются решить её через подсчёт дубликатов, но это приводит к багам. Мы разберём элегантный подход с инвариантом, который обобщается на любое число k допустимых повторений. Вы не просто запомните код, а поймёте, как задавать правильный вопрос на каждом шаге.
Дан отсортированный массив, например [0, 0, 1, 1, 1, 1, 2, 3, 3]. Нужно преобразовать его так, чтобы каждое значение встречалось не более двух раз, а остаток массива можно игнорировать. Результат: [0, 0, 1, 1, 2, 3, 3, _, _]. Важно: модификация должна быть in-place, O(1) дополнительной памяти, и вернуть новую длину.
Первый и второй элементы всегда валидны — их можно сразу записывать. Начиная с индекса 2, каждый раз перед записью мы задаём вопрос: «Могу ли я записать текущее значение в позицию writePointer?» Ответ даёт сравнение с элементом на две позиции назад от writePointer. Если значения различаются — запись разрешена, иначе — пропускаем.
Почему именно две позиции? Потому что мы допускаем не более двух одинаковых значений подряд. Если бы допускалось k повторений, мы сравнивали бы с writePointer - k.
Массив: [0, 0, 1, 1, 1, 1, 2, 3, 3]. Два указателя: readPointer (r) и writePointer (w). Начинаем оба с индекса 2.
Итоговая длина: 7. Первые 7 элементов: [0,0,1,1,2,3,3].
def remove_duplicates(nums):
# writePointer указывает, куда записывать следующий валидный элемент
writePointer = 0
for readPointer in range(len(nums)):
# Первые два элемента всегда можно записать
# Или если текущее значение отличается от элемента на две позиции назад
if readPointer < 2 or nums[readPointer] != nums[writePointer - 2]:
nums[writePointer] = nums[readPointer]
writePointer += 1
return writePointer
# Пример использования
arr = [0, 0, 1, 1, 1, 1, 2, 3, 3]
new_len = remove_duplicates(arr)
print(arr[:new_len]) # [0, 0, 1, 1, 2, 3, 3]Временная сложность: O(n). Дополнительная память: O(1).
Если бы задача допускала не более k повторений, условие записи изменилось бы на readPointer < k or nums[readPointer] != nums[writePointer - k]. Всё остальное остаётся неизменным.
Прямо сейчас откройте LeetCode или любой редактор и реализуйте этот алгоритм на Python. Затем измените условие на «не более трёх повторений» и убедитесь, что достаточно поменять одно число. Если вы застряли на похожей задаче — не пытайтесь угадать решение, а задайте себе вопрос: «Какой инвариант гарантирует корректность?»
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →