Move Zeroes
Дан массив целых чисел nums. Переместите все нули в конец массива, сохранив порядок остальных значений. Верните переставленный массив, длина которого совпадает с длиной nums.
Функция
- numsinteger-array
- массив целых чисел для перестановки
- Возвращаетinteger-array
- nums с ненулевыми значениями в начале, в исходном порядке, а все 0 — в конце
Ограничения
1 ≤ nums.length ≤ 5000-105 ≤ nums[i] ≤ 105
Примеры
- Ввод
- nums = [0, 4, 0, 7, 2]
- Вывод
- [4, 7, 2, 0, 0]
- Пояснение
- Значения, не равные 0, — это 4, 7 и 2, и они сохраняют этот порядок в начале. Два нуля занимают последние два места.
- Ввод
- nums = [-3, 8, 1]
- Вывод
- [-3, 8, 1]
- Пояснение
- Перемещать нечего: 0 нет, поэтому массив возвращается без изменений. -3 — отрицательное число, а не ноль, поэтому оно остаётся первым.
- Ввод
- nums = [0]
- Вывод
- [0]
- Пояснение
- Массив, содержащий один 0, уже имеет окончательный вид.
+14 скрытых тестов при отправке
Дополнительный вопрос
Можешь вместо этого переместить все 0 в начало, сохранив порядок остальных значений, за один проход и с дополнительной памятью O(1)?
Подсказки
Открывайте по одной. Каждая подсказывает чуть больше.
Представь готовый массив: ненулевые значения в прежнем порядке, а затем нули. Где должно оказаться первое ненулевое значение, которое ты встретишь?
Храни индекс
writeдля следующего свободного места в начале. Каждое ненулевое значение, которое ты встретишь, помещай именно туда, а затем перемещай место на одну позицию вправо.Перемещайтесь с помощью второго индекса
read. Когдаnums[read]не равно 0, поменяйте его местами сnums[write]и передвиньтеwriteвперёд. Между двумя индексами всегда находятся только нули, поэтому каждая перестановка сдвигает 0 назад и сохраняет порядок остальных значений.
Решение
Переместить нули в конец — несложно. Сложность в том, чтобы сохранить исходный порядок остальных значений, поэтому нельзя менять каждый 0 местами с последним элементом. Разделите массив на начальную часть, в которой находятся все найденные на данный момент ненулевые значения, и оставшуюся часть. Один индекс проходит по каждому элементу, второй отмечает, куда нужно поместить следующее ненулевое значение, а одного прохода достаточно, чтобы выполнить задачу на месте.
Скопируйте ненулевые значения
Идея
Создай новый массив. Пройди по nums и скопируй каждое значение, которое не равно 0, в том порядке, в котором ты его встречаешь. Затем добавь нули, пока длина нового массива не станет такой же, как у nums. Количество добавленных нулей равно количеству пропущенных элементов.
Для [0, 4, 0, 7, 2] на этапе копирования получится [4, 7, 2], а два нуля дадут [4, 7, 2, 0, 0]. Порядок сохраняется, потому что ты копируешь значения в том порядке, в котором их читаешь.
Каждый элемент читается один раз и записывается один раз, поэтому временная сложность — O(n). Второй массив требует O(n) памяти, от которой позволяет избавиться следующий подход.
Алгоритм
- Создай пустой массив результата.
- Для каждого значения в
numsдобавь его в результат, если оно не равно 0. - Добавляй нули, пока в результате не будет столько же элементов, сколько в
nums. - Верни результат.
def moveZeroes(nums):
result = [x for x in nums if x != 0]
result += [0] * (len(nums) - len(result))
return resultДва указателя, обмен элементов на месте
Идея
Используйте два индекса. read проходит по каждому элементу слева направо. write указывает, где должно находиться следующее ненулевое значение. После каждого шага выполняются два условия: все элементы перед write — это уже встреченные ненулевые значения в исходном порядке, а все элементы от write до read — это 0.
Когда nums[read] не равен 0, поменяйте его местами с nums[write] и сдвиньте write на один шаг вправо. Значение, которое оказывается на месте read, — это 0 из зоны нулей либо то же самое значение, если оба индекса равны. Ненулевые значения перепрыгивают только через нули, никогда — друг через друга, поэтому их порядок сохраняется.
Для [0, 4, 0, 7, 2]: 4 с индексом 1 меняется местами с элементом по индексу 0, в результате получается [4, 0, 0, 7, 2]. 7 с индексом 3 меняется местами с элементом по индексу 1, в результате получается [4, 7, 0, 0, 2]. 2 с индексом 4 меняется местами с элементом по индексу 2, в результате получается [4, 7, 2, 0, 0]. Один проход и без второго массива: время O(n), память O(1).
Алгоритм
- Установи
writeв 0. - Перемещай
readот первого индекса к последнему. - Если
nums[read]не равно 0, поменяй местамиnums[read]иnums[write], затем увеличьwriteна 1. - Верни
nums.
def moveZeroes(nums):
write = 0 # nums[:write] holds the non-zero values found so far, in order
for read in range(len(nums)):
if nums[read] != 0:
nums[write], nums[read] = nums[read], nums[write]
write += 1
return nums
Ловушки и крайние случаи
Обычные ошибки либо нарушают порядок остальных значений, либо пропускают элементы.
- Замена каждого 0 последним элементом перемещает нули, но перемешивает остальные элементы:
[0, 4, 7]становится[7, 4, 0]. - Удаление нулей из массива, пока индекс проходит по нему, приводит к пропуску элементов. В
[0, 0, 5]удаление элемента с индексом 0 сдвигает второй 0 на индекс 0, а цикл переходит к индексу 1. Каждое удаление также сдвигает остальные элементы массива, из-за чего цикл работает за O(n²). - Проверяйте
x != 0, а неx > 0. Отрицательные значения — не нули:[-1, 0, -2]должно превратиться в[-1, -2, 0], но при проверкеx > 0версия с копированием возвращает[0, 0, 0]. - Массив без нулей или состоящий только из нулей должен остаться без изменений. В версии с обменом
readиwriteостаются равными до первого 0, поэтому эти обмены ничего не меняют. - В Lua и R индексация массивов начинается с 1, поэтому
writeтоже начинается с 1.
Частые вопросы4
Какова временная сложность Move Zeroes?
O(n). Оба подхода считывают каждый элемент один раз. Копирование ненулевых значений в новый массив требует дополнительной памяти O(n), а обмен с двумя указателями выполняется внутри массива и требует дополнительной памяти O(1).
Как переместить нули в конец, не меняя порядок остальных элементов?
Храни индекс write для следующего свободного места в начале и выполняй сканирование с помощью второго индекса. Каждое найденное ненулевое значение меняется местами с элементом на позиции write, а write сдвигается на одну позицию вправо. Значения размещаются в том порядке, в котором ты их находишь, поэтому их взаимный порядок никогда не меняется.
Можно ли решить задачу Move Zeroes с меньшим количеством записей?
Да. Вместо обмена скопируй каждое ненулевое значение в nums[write], а после прохода заполни все позиции от write до конца нулями. Так каждая позиция будет записана не более одного раза. Также можно пропустить обмен, если read равно write, поскольку значение вернётся на то же место.
Почему задача «Переместить нули» решается с помощью двух указателей?
Один указатель считывает каждый элемент, а другой отмечает конец готовой передней части. Оба перемещаются только вперёд, поэтому вместе выполняют один проход. Тот же шаблон чтения и записи удаляет дубликаты из отсортированного массива или фильтрует любое значение из массива на месте.
Похожие задачи
Задачи на те же идеи. Решите две или три, и приём запомнится.
Python
def moveZeroes(nums):
# Напишите код здесьСлучай 1
Случай 2
Случай 3
Ввод
nums = [0, 4, 0, 7, 2]
Ожидается
[4, 7, 2, 0, 0]