Given an array of integers, modify the array in place to move all zeros to the end while maintaining the relative order of non-zero elements.
Input: nums = [0, 1, 0, 3, 2]
Output: [1, 3, 2, 0, 0]
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
Imagine a shelf where some slots hold books (non-zeros) and some are empty gaps (zeros). You want all the books pushed to the left in their original order, with the gaps at the right. You walk along the shelf with one hand (the scanner). Every time you find a book, you slide it back to the first free slot, tracked by your other hand (the placer). When you finish walking, all books are packed on the left and the gaps naturally ended up on the right.
This problem has three main requirements:
A naive approach to this problem is to build the output using a separate array (temp). We can add all non-zero elements from the left of nums to this temporary array and leave the rest of it as zeros. Then, we just set the input array equal to temp.
By identifying and moving the non-zero elements from the left side of the array first, we ensure their order is preserved when we add them to the output:
def shift_zeros_to_the_end_naive(nums: List[int]) -> None:
temp = [0] * len(nums)
i = 0
# Add all non-zero elements to the left of 'temp'.
for num in nums:
if num != 0:
temp[i] = num
i += 1
# Set 'nums' to 'temp'.
for j in range(len(nums)):
nums[j] = temp[j]#include <vector>
using namespace std;
void shiftZerosToTheEndNaive(vector<int>& nums) {
vector<int> nonZeros;
for (int x : nums) {
if (x != 0) nonZeros.push_back(x); // keep non-zeros, in order
}
int write = 0;
for (int x : nonZeros) {
nums[write++] = x; // place them at the front
}
while (write < (int)nums.size()) {
nums[write++] = 0; // pad the rest with zeros
}
}// This method lives inside a class, e.g. class Solution { ... }
public void shiftZerosToTheEndNaive(int[] nums) {
int[] temp = new int[nums.length]; // new arrays start filled with 0
int write = 0;
for (int x : nums) {
if (x != 0) temp[write++] = x; // keep non-zeros, in order
}
for (int i = 0; i < nums.length; i++) {
nums[i] = temp[i]; // copy back (tail is already 0)
}
}Unfortunately, this solution breaks the third requirement of modifying the input array in place.
However, there's still valuable insight to be gained from this approach. In particular, notice that this solution focuses on the non-zero elements instead of zeros. This means if we change our goal to move all non-zero elements to the left of the array, the zeros will consequently end up on the right. Therefore, we only need to focus on non-zero elements:
If there was a way to iterate over the above range of the array where the non-zero elements go, we could iteratively place each non-zero element in that range.
Two pointers
We can use two pointers for this:
Consider the example below. Start by placing the left and right pointers at the start of the array. Before we move non-zero elements to the left, we need the right pointer to be pointing at a non-zero element. So, we ignore the zero at the first element and increment right:
Remember, we only use the left pointer to keep track of where non-zero elements should be placed. So, until we find a non-zero element, we shouldn’t move this pointer.
Now, the value at the right pointer is non-zero. Let’s discuss how to handle this case.
1. Swap the elements at left and right: First, we’d like to move the element at the right pointer to the left of the array. So, we swap it with the element at the left pointer.
2. Increment the pointers:
We can apply this logic to the rest of the array, incrementing the right pointer at each step to find the next non-zero element:
Once all swapping is done, all zeros will end up at the right end of the array as intended, without disturbing the order of the non-zero elements. The two-pointer strategy used in this problem is unidirectional traversal.
You might have noticed that we always move the right pointer forward, regardless of whether it points to a zero or a non-zero. This allows us to use a for-loop to iterate the right pointer.
def shift_zeros_to_the_end(nums: List[int]) -> None:
# The 'left' pointer is used to position non-zero elements.
left = 0
# Iterate through the array using a 'right' pointer to locate non-zero
# elements.
for right in range(len(nums)):
if nums[right] != 0:
if right != left:
nums[left], nums[right] = nums[right], nums[left]
# Increment 'left' since it now points to a position already occupied
# by a non-zero element.
left += 1#include <vector>
using namespace std;
void shiftZerosToTheEnd(vector<int>& nums) {
int left = 0; // next slot for a non-zero value
int n = nums.size();
for (int right = 0; right < n; right++) {
if (nums[right] != 0) {
// Swap the non-zero value into the 'left' slot.
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
}
}
}// This method lives inside a class, e.g. class Solution { ... }
public void shiftZerosToTheEnd(int[] nums) {
int left = 0; // next slot for a non-zero value
for (int right = 0; right < nums.length; right++) {
if (nums[right] != 0) {
// Swap the non-zero value into the 'left' slot.
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
}
}
}Time complexity: The time complexity of shift_zeros_to_the_end is , where denotes the length of the array. This is because we iterate through the input array once.
Space complexity: The space complexity is because shifting is done in place.
Test Cases
In addition to the examples discussed, below are more examples to consider when testing your code.
| Input | Expected output | Description |
|---|---|---|
nums = [] | [] | Tests an empty array. |
nums = [0] | [0] | Tests an array with one 0. |
nums = [1] | [1] | Tests an array with one 1. |
nums = [0, 0, 0] | [0, 0, 0] | Tests an array with all 0s. |
nums = [1, 3, 2] | [1, 3, 2] | Tests an array with all non-zeros. |
nums = [1, 1, 1, 0, 0] | [1, 1, 1, 0, 0] | Tests an array with all zeros already at the end. |
nums = [0, 0, 1, 1, 1] | [1, 1, 1, 0, 0] | Tests an array with all zeros at the start. |
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
left = placement slot right = scanner
Result: [1, 3, 2, 0, 0] — non-zeros keep their order (1, 3, 2) and zeros are at the end.
| right | nums[right] | left (before) | Action | Array after |
|---|---|---|---|---|
| 0 | 0 | 0 | zero → skip | [0, 1, 0, 3, 2] |
| 1 | 1 | 0 | swap(0,1), left→1 | [1, 0, 0, 3, 2] |
| 2 | 0 | 1 | zero → skip | [1, 0, 0, 3, 2] |
| 3 | 3 | 1 | swap(1,3), left→2 | [1, 3, 0, 0, 2] |
| 4 | 2 | 2 | swap(2,4), left→3 | [1, 3, 2, 0, 0] |
left = placement slot right = scanner
Result: [6, 1, 4, 0, 0, 0] — the non-zeros keep their order (6, 1, 4) and the four zeros land at the end.
| right | nums[right] | left (before) | Action | Array after |
|---|---|---|---|---|
| 0 | 0 | 0 | zero → skip | [0, 0, 6, 1, 0, 4] |
| 1 | 0 | 0 | zero → skip | [0, 0, 6, 1, 0, 4] |
| 2 | 6 | 0 | swap(0,2), left→1 | [6, 0, 0, 1, 0, 4] |
| 3 | 1 | 1 | swap(1,3), left→2 | [6, 1, 0, 0, 0, 4] |
| 4 | 0 | 2 | zero → skip | [6, 1, 0, 0, 0, 4] |
| 5 | 4 | 2 | swap(2,5), left→3 | [6, 1, 4, 0, 0, 0] |
The bigger the gap between left and right, the further a non-zero jumps — here the 4 leaps from index 5 to index 2 in a single swap.
Both runs step exactly as the tables above. Python-specific: the swap is one line — nums[left], nums[right] = nums[right], nums[left].
Same steps. C++-specific: swap through a temp variable; when left == right the swap harmlessly puts a value back on itself.
Same steps. Java-specific: swap through a temp variable; the left == right self-swap is likewise harmless.