A two-pointer pattern that modifies the array in-place with minimal writes.
Given a list, move all zeros to the end while maintaining the relative order of non-zero elements. Do it in-place without making a copy of the list.
# [0,1,0,3,12] → [1,3,12,0,0]
# [0,0,1] → [1,0,0]
# [0] → [0]
def move_zeros_naive(nums):
non_zeros = [x for x in nums if x != 0]
zeros = [0] * (len(nums) - len(non_zeros))
nums[:] = non_zeros + zeros # Modify in-place via slice assignment
Clean and readable but creates extra lists — O(n) space. The in-place requirement means we should aim for O(1) extra space.
Keep a write pointer that tracks where the next non-zero should go. Walk a read pointer through the array; when it finds a non-zero, write it at write and advance both. Finally, fill the rest with zeros.
def move_zeros(nums):
write = 0 # next position for a non-zero element
# Pass 1: compact all non-zeros to the front
for read in range(len(nums)):
if nums[read] != 0:
nums[write] = nums[read]
write += 1
# Pass 2: fill the tail with zeros
for i in range(write, len(nums)):
nums[i] = 0
a = [0, 1, 0, 3, 12]
move_zeros(a)
print(a) # [1, 3, 12, 0, 0]
| Property | Value |
|---|---|
| Time | O(n) |
| Space | O(1) — truly in-place |
Input: [0, 1, 0, 3, 12]
| read | nums[read] | Action | write | Array |
|---|---|---|---|---|
| 0 | 0 | Skip (zero) | 0 | [0,1,0,3,12] |
| 1 | 1 | Write 1 at [0], write++ | 1 | [1,1,0,3,12] |
| 2 | 0 | Skip (zero) | 1 | [1,1,0,3,12] |
| 3 | 3 | Write 3 at [1], write++ | 2 | [1,3,0,3,12] |
| 4 | 12 | Write 12 at [2], write++ | 3 | [1,3,12,3,12] |
Fill from index 3 onward with zeros → [1, 3, 12, 0, 0]
Instead of writing and then zeroing, swap each non-zero with the element at write. Zeros are automatically pushed back without a second pass. Useful when writes are expensive.
def move_zeros_swap(nums):
write = 0
for read in range(len(nums)):
if nums[read] != 0:
nums[write], nums[read] = nums[read], nums[write]
write += 1
b = [0, 1, 0, 3, 12]
move_zeros_swap(b)
print(b) # [1, 3, 12, 0, 0]
write == read early on) but avoids the second zeroing pass. Both approaches are O(n) time, O(1) space.