033. Move Zeros Stably
Problem
Move all zeros to the end of a mutable integer list, preserving the relative order of nonzero values. Return the same list, with its original length. Negative numbers are nonzero and must be retained in order.
Examples
Example 1
move_zeros([0, 1, 0, 3, 12])
=> [1, 3, 12, 0, 0]Nonzero order is unchanged.
Example 2
move_zeros([])
=> []Both phases do no work.
Example 3
move_zeros([0, -1, 0, -2])
=> [-1, -2, 0, 0]Negative values are ordinary retained data.
Reveal the worked solution and reasoning
Why this mechanism works
First compact accepted values into a prefix. Because write never exceeds the read position, each write is safe. After all original values have been read, every remaining position can be filled with zero. The second phase avoids swapping zeros repeatedly through the list.
State and boundaries
write is the count of nonzero values consumed; nums[:write] is their stable sequence.
Protocol
- Scan original values.
- Write each nonzero at write and advance write.
- After the scan fill the remaining suffix with zeros.
- Return nums.
Visualization
[0,1,0,3,12]
compact: [1,3,12 | ?,?]
fill: [1,3,12 | 0,0]Python implementation
def move_zeros(nums):
write = 0
for value in nums:
if value != 0:
nums[write] = value
write += 1
for i in range(write, len(nums)):
nums[i] = 0
return nums
Worked execution
move_zeros([0, 1, 0, 3, 12])
L2 move_zeros: write = 0 nums=[0, 1, 0, 3, 12]; write=0 L3 move_zeros: for value in nums: nums=[0, 1, 0, 3, 12]; write=0; value=0 L3 move_zeros: for value in nums: nums=[0, 1, 0, 3, 12]; write=0; value=1 L5 move_zeros: nums[write] = value nums=[1, 1, 0, 3, 12]; write=0; value=1 L6 move_zeros: write += 1 nums=[1, 1, 0, 3, 12]; write=1; value=1 L3 move_zeros: for value in nums: nums=[1, 1, 0, 3, 12]; write=1; value=0 L3 move_zeros: for value in nums: nums=[1, 1, 0, 3, 12]; write=1; value=3 L5 move_zeros: nums[write] = value nums=[1, 3, 0, 3, 12]; write=1; value=3 ... 4 intermediate state changes omitted ... L7 move_zeros: for i in range(write, len(nums)): nums=[1, 3, 12, 3, 12]; write=3; value=12; i=3 L8 move_zeros: nums[i] = 0 nums=[1, 3, 12, 0, 12]; write=3; value=12; i=3 L7 move_zeros: for i in range(write, len(nums)): nums=[1, 3, 12, 0, 12]; write=3; value=12; i=4 L8 move_zeros: nums[i] = 0 nums=[1, 3, 12, 0, 0]; write=3; value=12; i=4
Result: [1, 3, 12, 0, 0]. Nonzero order is unchanged.
move_zeros([])
L2 move_zeros: write = 0 nums=[]; write=0
Result: []. Both phases do no work.
Why the solution is correct
The first phase appends nonzero values in encounter order without overwriting unread data. The number of unwritten positions equals the number of zeros. Filling exactly that suffix preserves length and multiplicity.
Time and space
O(n) time and O(1) auxiliary space.
Common mistakes
Swapping arbitrary nonzero endpoints can break stability. Filling zeros during the read scan can overwrite values not yet examined.
Reasoning card
State: write is the count of nonzero values consumed; nums[:write] is their stable sequence.
Invariant: The prefix before write is the final compacted output.
Decision to rehearse: Separate read position from next write position.
Finish: Return nums
Transfer exercise
Generalize zero to a discard predicate and state whether the discarded suffix must preserve order too.