283. Move Zeroes
Problem Description
You are given an integer array nums
that contains both zero and non-zero elements. Your task is to rearrange the array by moving all zeros to the end while keeping the relative order of non-zero elements unchanged.
For example, if the input array is [0, 1, 0, 3, 12]
, after moving zeros to the end, it should become [1, 3, 12, 0, 0]
. Notice that the non-zero elements 1, 3, 12
maintain their original relative order.
The key requirements are:
- All zeros must be moved to the end of the array
- The relative order of non-zero elements must be preserved (if element
a
appears before elementb
in the original array, and both are non-zero, thena
must still appear beforeb
in the modified array) - You must modify the array in-place without creating a copy of the array
- The operation should be done directly on the input array
nums
The solution uses a two-pointer technique where pointer k
tracks the position where the next non-zero element should be placed. As we iterate through the array with index i
, whenever we find a non-zero element at position i
, we swap it with the element at position k
and increment k
. This ensures that all non-zero elements are moved to the front of the array in their original order, while zeros naturally end up at the end.
Intuition
When we need to move all zeros to the end while maintaining the relative order of non-zero elements, we can think of it as partitioning the array into two sections: a section for non-zero elements at the front and a section for zeros at the back.
The key insight is that we don't actually need to explicitly "move" zeros. Instead, we can focus on placing all non-zero elements in their correct positions at the beginning of the array. Once all non-zero elements are positioned correctly at the front, the zeros will naturally occupy the remaining positions at the end.
Think of it like organizing a bookshelf: instead of removing all the empty spaces first and then rearranging books, we can simply slide each book to the leftmost available position. The empty spaces will automatically end up on the right side.
This leads us to maintain a pointer k
that represents the "next available position" for a non-zero element. As we scan through the array:
- When we encounter a non-zero element at position
i
, we know it should be placed at positionk
- We swap the elements at positions
i
andk
- We increment
k
to point to the next available position
Why does swapping work instead of just overwriting? Because if k
and i
are at different positions, the element at position k
is either:
- A zero that needs to move to the back anyway
- Already processed (when
k = i
), in which case swapping with itself doesn't change anything
This approach elegantly handles all cases while maintaining the relative order of non-zero elements and achieving the goal in a single pass through the array with O(1)
extra space.
Solution Approach
We implement the two-pointer technique to solve this problem efficiently in a single pass through the array.
Algorithm Steps:
-
Initialize the pointer: We start with a pointer
k = 0
which represents the position where the next non-zero element should be placed. -
Iterate through the array: We traverse the array using
enumerate(nums)
to get both the indexi
and valuex
of each element. -
Process non-zero elements: For each element
x
:- If
x
is non-zero (the conditionif x:
evaluates toTrue
for any non-zero number) - We swap the current element at position
i
with the element at positionk
using Python's tuple unpacking:nums[k], nums[i] = nums[i], nums[k]
- We increment
k
by 1 to point to the next available position for a non-zero element
- If
Why this works:
- When we encounter a non-zero element, we place it at position
k
, which maintains the relative order since we're processing elements sequentially - The swap operation ensures that:
- If
k == i
, we're swapping an element with itself (no change) - If
k < i
, we're moving a non-zero element forward and sending a zero (or already processed element) backward
- If
- After processing all elements, the first
k
positions contain all non-zero elements in their original order, and positions fromk
to the end contain all zeros
Time and Space Complexity:
- Time Complexity:
O(n)
wheren
is the length of the array, as we make a single pass through the array - Space Complexity:
O(1)
as we only use a constant amount of extra space (the pointerk
)
The beauty of this solution lies in its simplicity - by maintaining just one pointer and using swaps, we achieve the desired result without any complex logic or additional data structures.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the array [0, 1, 0, 3, 12]
.
Initial Setup:
- Array:
[0, 1, 0, 3, 12]
- Pointer
k = 0
(next position for non-zero element)
Step 1: i = 0, x = 0
- Element is 0 (zero), so we skip it
- Array:
[0, 1, 0, 3, 12]
k
remains at 0
Step 2: i = 1, x = 1
- Element is 1 (non-zero), so we swap nums[k] with nums[i]
- Swap nums[0] with nums[1]:
[1, 0, 0, 3, 12]
- Increment k:
k = 1
Step 3: i = 2, x = 0
- Element is 0 (zero), so we skip it
- Array:
[1, 0, 0, 3, 12]
k
remains at 1
Step 4: i = 3, x = 3
- Element is 3 (non-zero), so we swap nums[k] with nums[i]
- Swap nums[1] with nums[3]:
[1, 3, 0, 0, 12]
- Increment k:
k = 2
Step 5: i = 4, x = 12
- Element is 12 (non-zero), so we swap nums[k] with nums[i]
- Swap nums[2] with nums[4]:
[1, 3, 12, 0, 0]
- Increment k:
k = 3
Final Result: [1, 3, 12, 0, 0]
Notice how:
- The pointer
k
always marks where the next non-zero should go - Non-zero elements (1, 3, 12) maintain their original relative order
- All zeros naturally end up at the end of the array
- We achieved this with just one pass and constant extra space
Solution Implementation
1class Solution:
2 def moveZeroes(self, nums: List[int]) -> None:
3 """
4 Moves all zeros to the end of the array while maintaining
5 the relative order of non-zero elements.
6 Modifies the array in-place.
7
8 Args:
9 nums: List of integers to be modified in-place
10 """
11 # Pointer to track the position where next non-zero element should be placed
12 non_zero_position = 0
13
14 # Iterate through each element in the array
15 for current_index, current_value in enumerate(nums):
16 # If current element is non-zero
17 if current_value != 0:
18 # Swap current non-zero element with element at non_zero_position
19 nums[non_zero_position], nums[current_index] = nums[current_index], nums[non_zero_position]
20 # Move the non-zero position pointer forward
21 non_zero_position += 1
22
1class Solution {
2 public void moveZeroes(int[] nums) {
3 // Pointer to track the position where next non-zero element should be placed
4 int nonZeroIndex = 0;
5 int arrayLength = nums.length;
6
7 // Iterate through the entire array
8 for (int currentIndex = 0; currentIndex < arrayLength; currentIndex++) {
9 // If current element is non-zero, swap it with element at nonZeroIndex position
10 if (nums[currentIndex] != 0) {
11 // Swap current non-zero element with element at nonZeroIndex
12 int temp = nums[currentIndex];
13 nums[currentIndex] = nums[nonZeroIndex];
14 nums[nonZeroIndex] = temp;
15
16 // Move nonZeroIndex pointer forward for next non-zero element
17 nonZeroIndex++;
18 }
19 }
20 }
21}
22
1class Solution {
2public:
3 void moveZeroes(vector<int>& nums) {
4 // Position to place the next non-zero element
5 int nonZeroPosition = 0;
6 int arraySize = nums.size();
7
8 // Iterate through the entire array
9 for (int currentIndex = 0; currentIndex < arraySize; ++currentIndex) {
10 // If current element is non-zero
11 if (nums[currentIndex] != 0) {
12 // Swap current non-zero element with the element at nonZeroPosition
13 // This moves non-zero elements to the front while maintaining order
14 swap(nums[currentIndex], nums[nonZeroPosition]);
15 // Move the position for next non-zero element
16 nonZeroPosition++;
17 }
18 }
19 // After the loop, all non-zero elements are at the beginning in their original order,
20 // and all zeros are automatically moved to the end
21 }
22};
23
1/**
2 * Moves all zeros in the array to the end while maintaining the relative order of non-zero elements.
3 * Modifies the array in-place without returning anything.
4 *
5 * @param nums - The input array of numbers to be modified
6 */
7function moveZeroes(nums: number[]): void {
8 // Keep track of the position where the next non-zero element should be placed
9 let nonZeroIndex = 0;
10
11 // Iterate through the entire array
12 for (let currentIndex = 0; currentIndex < nums.length; currentIndex++) {
13 // Check if the current element is non-zero
14 if (nums[currentIndex] !== 0) {
15 // Swap the current non-zero element with the element at nonZeroIndex position
16 // This moves non-zero elements to the front while pushing zeros to the back
17 [nums[currentIndex], nums[nonZeroIndex]] = [nums[nonZeroIndex], nums[currentIndex]];
18
19 // Move the pointer for the next non-zero element position
20 nonZeroIndex++;
21 }
22 }
23}
24
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array nums
. The algorithm iterates through the array exactly once using a single for loop with enumerate()
, performing constant-time operations (comparison and swap) for each element.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space - a single integer variable k
to track the position where the next non-zero element should be placed, and the loop variables i
and x
. The swapping operation is done in-place without requiring any additional data structures that scale with input size.
Common Pitfalls
1. Unnecessary Operations When No Zeros Exist
The current solution performs swaps even when non_zero_position == current_index
, which happens when there are no zeros encountered yet. This leads to redundant self-swaps that don't change the array but still consume CPU cycles.
Example: For array [1, 2, 3, 4, 5]
with no zeros, the algorithm performs 5 unnecessary swap operations where each element swaps with itself.
Solution: Add a condition to only swap when the pointers are different:
if current_value != 0: if non_zero_position != current_index: nums[non_zero_position], nums[current_index] = nums[current_index], nums[non_zero_position] non_zero_position += 1
2. Misunderstanding the Two-Pointer Concept
A common mistake is trying to use two pointers from opposite ends of the array (one from start, one from end). This approach breaks the relative order requirement because elements from the end would be moved to earlier positions.
Wrong Approach Example:
# DON'T DO THIS - breaks relative order
left, right = 0, len(nums) - 1
while left < right:
if nums[left] == 0:
nums[left], nums[right] = nums[right], nums[left]
right -= 1
else:
left += 1
This would transform [0, 1, 0, 3, 12]
into something like [12, 1, 3, 0, 0]
, breaking the relative order of non-zero elements.
3. Creating a New Array Instead of In-Place Modification
Some might be tempted to create a new array to store non-zero elements first, then append zeros:
# DON'T DO THIS - violates space complexity requirement
non_zeros = [x for x in nums if x != 0]
zeros = [0] * (len(nums) - len(non_zeros))
nums[:] = non_zeros + zeros # Even though we assign back, we used O(n) extra space
4. Using Multiple Passes Unnecessarily
Another pitfall is making the solution more complex with multiple passes:
# Inefficient approach with multiple passes
# First pass: count zeros
zero_count = nums.count(0)
# Second pass: collect non-zeros
write_pos = 0
for num in nums:
if num != 0:
nums[write_pos] = num
write_pos += 1
# Third pass: fill remaining with zeros
for i in range(write_pos, len(nums)):
nums[i] = 0
While this works, it's less elegant and requires multiple iterations when one suffices.
5. Forgetting Edge Cases
Don't forget to consider:
- Empty array
[]
- Array with all zeros
[0, 0, 0]
- Array with no zeros
[1, 2, 3]
- Single element arrays
[0]
or[1]
The original solution handles all these cases correctly, but modifications might introduce bugs if not carefully tested against these scenarios.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donโt Miss This!