In-place modification

In-place modification — это когда ты меняешь уже существующую структуру данных на месте, а не создаёшь новую копию.

То есть не так:

new_arr = []
for x in arr:
    if x != 0:
        new_arr.append(x)

А так:

write = 0

for read in range(len(arr)):
    if arr[read] != 0:
        arr[write] = arr[read]
        write += 1

Здесь массив arr остаётся тем же самым объектом, но его содержимое переписывается внутри.

Есть уже выделенная память под массив / список / строку / матрицу.

In-place значит:

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

Обычно это означает O(1) дополнительной памяти или почти O(1).

Например:

arr = [1, 2, 3, 4]
arr.reverse()
print(arr)

Результат:

[4, 3, 2, 1]

Это in-place, потому что изменился сам arr.

А вот так — не in-place:

arr = [1, 2, 3, 4]
reversed_arr = arr[::-1]

Тут создан новый список.

Почему это важно в алгоритмах

Очень часто задача говорит:

Modify the array in-place.

Это значит: не надо возвращать новый массив, надо изменить исходный.

Например задача:

Remove duplicates from sorted array in-place.

То есть у тебя есть:

nums = [1, 1, 2, 2, 3]

Нужно превратить начало массива в:

[1, 2, 3, ...]

Но при этом не создавать новый массив.

Типичный паттерн:

def remove_duplicates(nums):
    if not nums:
        return 0

    write = 1

    for read in range(1, len(nums)):
        if nums[read] != nums[read - 1]:
            nums[write] = nums[read]
            write += 1

    return write

После этого:

nums = [1, 1, 2, 2, 3]
k = remove_duplicates(nums)

print(k)        # 3
print(nums[:k]) # [1, 2, 3]

Важно: хвост массива после k обычно считается мусором. Он может быть каким угодно:

[1, 2, 3, 2, 3]

Смысл только в первых k элементах.

Самый частый паттерн: read / write pointer

In-place modification очень часто делается через два указателя:

read  # откуда читаем
write # куда пишем

read идёт по старым данным.

write показывает место, куда надо положить следующий правильный элемент.

Например убрать все нули:

def move_zeroes(nums):
    write = 0

    for read in range(len(nums)):
        if nums[read] != 0:
            nums[write] = nums[read]
            write += 1

    while write < len(nums):
        nums[write] = 0
        write += 1

Было:

[0, 1, 0, 3, 12]

Стало:

[1, 3, 12, 0, 0]

И это сделано без нового массива.

Ещё один паттерн: swap

Если порядок не важен, можно менять элементы местами.

Например удалить элемент val:

def remove_element(nums, val):
    i = 0
    n = len(nums)

    while i < n:
        if nums[i] == val:
            nums[i] = nums[n - 1]
            n -= 1
        else:
            i += 1

    return n

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

Это тоже in-place.

В чём подвох

In-place modification — это не просто “экономия памяти”. Это ещё и изменение самого объекта.

Например:

a = [1, 2, 3]
b = a

a.reverse()

print(b)

Будет:

[3, 2, 1]

Потому что a и b указывают на один и тот же список.

Вот тут важное различение:

a = [1, 2, 3]
b = a

b — не копия. Это ещё одна ссылка на тот же объект.

А вот:

b = a[:]

это уже копия.

In-place не всегда значит “лучше”

Плюсы:

  • меньше памяти;
  • часто быстрее, потому что нет копирования;
  • требуется в задачах на массивы;
  • полезно для больших данных.

Минусы:

  • исходные данные уничтожаются / меняются;
  • сложнее рассуждать;
  • больше риск багов с индексами;
  • если на структуру есть другие ссылки, они тоже увидят изменения.

То есть in-place — это не моральная добродетель, а конкретный технический режим: работаем внутри уже существующей формы.

Как это распознать в задачах

Фразы-маркеры:

modify the array in-place
do not allocate extra space
use O(1) extra memory
return the length, not a new array
the order of elements may be changed

Если видишь такое — почти всегда нужен один из паттернов:

two pointers
read/write pointer
swap with end
reverse in-place
partition
cyclic sort

Простая формула

Не in-place:

result = transform(original)

In-place:

transform(original)
# original itself has changed

То есть в первом случае ты создаёшь новую реальность рядом.

Во втором — переписываешь текущую структуру изнутри.

Прокрутить вверх