283. Move Zeroes - Explanation

Problem Link

Description

You are given an integer array nums, move all 0's to the end of it while maintaining the relative order of the non-zero elements.

Note that you must do this in-place without making a copy of the array.

Example 1:

Input: nums = [0,0,1,2,0,5]

Output: [1,2,5,0,0,0]

Example 2:

Input: nums = [0,1,0]

Output: [1,0,0]

Constraints:

  • 1 <= nums.length <= 10,000
  • -(2^31) <= nums[i] <= ((2^31)-1)

Follow up: Could you minimize the total number of operations done?



Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Arrays - Understanding array indexing and in-place modification
  • Two Pointers - Using two pointers to partition or rearrange elements within an array

1. Extra Space

Intuition

The simplest approach is to separate non-zero elements from zeros using extra storage. We collect all non-zero elements first, then write them back to the original array, filling the remaining positions with zeros. This guarantees the relative order of non-zero elements is preserved.

Algorithm

  1. Create a temporary list tmp and add all non-zero elements from nums.
  2. Iterate through the original array:
    • For indices less than tmp.length, copy the value from tmp.
    • For remaining indices, set the value to 0.
class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        tmp = []
        for num in nums:
            if num != 0:
                tmp.append(num)

        for i in range(len(nums)):
            if i < len(tmp):
                nums[i] = tmp[i]
            else:
                nums[i] = 0

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

2. Two Pointers (Two Pass)

Intuition

We can avoid extra space by overwriting the array in place. Use a left pointer to track where the next non-zero element should go. As we scan with a right pointer, each non-zero element gets copied to the left pointer's position. After the first pass, all non-zero elements are at the front in order. A second pass fills the remaining positions with zeros.

Algorithm

  1. Initialize l = 0 to track the position for the next non-zero element.
  2. First pass: Iterate through the array with pointer r. For each non-zero element, copy it to nums[l] and increment l.
  3. Second pass: Fill positions from l to the end with 0.
class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        l = 0
        for r in range(len(nums)):
            if nums[r] != 0:
                nums[l] = nums[r]
                l += 1

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

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1)

3. Two Pointers (One Pass)

Intuition

Instead of copying values and then filling zeros separately, we can swap elements in a single pass. The left pointer marks the boundary between processed non-zero elements and unprocessed elements. When we encounter a non-zero element with the right pointer, we swap it with the element at the left pointer. This naturally pushes 0 to the right while keeping non-zero elements in their relative order.

Algorithm

  1. Initialize l = 0 to track the swap position.
  2. Iterate through the array with pointer r. For each non-zero element:
    • Swap nums[l] and nums[r].
    • Increment l.
  3. After the loop, all non-zero elements are at the front and zeros are at the end.
class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        l = 0
        for r in range(len(nums)):
            if nums[r]:
                nums[l], nums[r] = nums[r], nums[l]
                l += 1

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1)

Common Pitfalls

Swapping When Pointers Are at the Same Position

When both pointers l and r point to the same non-zero element, swapping is unnecessary and wastes operations. While this does not affect correctness, adding a check like if (l != r) before swapping improves efficiency and avoids redundant writes, which can matter for performance-sensitive applications.

Disrupting Relative Order of Non-Zero Elements

The problem requires maintaining the relative order of non-zero elements. Some approaches incorrectly swap non-zero elements with each other or move them out of sequence. The two-pointer technique works because the left pointer only advances when a non-zero element is placed, ensuring all non-zero elements shift left in their original order while zeros naturally accumulate at the end.