DSA sheet · Arrays · Two Pointers pattern

Move Zeroes

The fourth two-pointer problem, and the first where the two pointers move in the same direction instead of from opposite ends. The teacher starts by showing why sorting can't work (it breaks the order of the non-zero numbers). Then she gives a simple brute force with an extra array (O(n) time, but O(n) extra space), and finally the in-place two-pointer solution: a right pointer that reads every element and a left pointer that marks where the next non-zero number should go.

Why it matters: "one pointer reads, the other writes" is how you filter or compact an array in place. The same idea solves Remove Element, Remove Duplicates from Sorted Array, and the partition step of quicksort.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

What is a "pointer"?

A pointer here is a variable holding an index of the array. "Moving" it means adding 1 (or subtracting 1). Two-pointer solutions keep two such indexes and move them by simple rules, so one pass over the array is enough.

The brute force over all pairs, and why some shape lets us skip work

Many array questions could be solved by trying every pair of positions (two nested loops, about n²/2 pairs, so O(n²)). Two pointers avoids that by using something we know about the array. In Two Sum II and 3Sum, that "something" was sorting: it told us which pointer to move. In Move Zeroes we don't need sorting at all. The useful fact is simpler: we only ever need to move each non-zero once, to the next free spot on the left. So one pointer can remember that spot while the other one scans.

Shape 1: opposite-end pointers (Problems 1–2)

left starts at index 0, right at n − 1, and they walk toward each other. On a sorted array, moving left raises the value and moving right lowers it, so each move discards pairs that can't be the answer. Used for "find a pair with a sum".

Doubt: couldn't we use opposite ends here? Put left on a zero at the front, right on a non-zero at the back, and swap them?
→ That does push all zeros to the back, but it scrambles the order. [0, 1, 0, 3, 12]: swap the first 0 with 12 → [12, 1, 0, 3, 0], then the second 0 with 3 → [12, 1, 3, 0, 0]. The non-zeros are now 12, 1, 3 instead of 1, 3, 12. Wrong. This problem needs the pointers to move in the same direction.

Shape 2: same-direction pointers (this problem)

Both pointers start at index 0 and only move forward.

Everything before the slow pointer is the "finished" part. Because elements are written there in the order the fast pointer meets them, their relative order never changes.

picture130012grey = finished non-zeros, then zeros, then not read yet
  L R

The other shapes on this sheet

shapehow the pointers moveexample
Three pointers (low/mid/high)mid scans; small values are swapped to the low zone, big ones to the high zoneSort Colors
Fix one + two pointersa loop fixes one value, opposite-end pointers find the other two; equal values are skipped3Sum
Opposite ends + left-max/right-maxkeep the tallest bar from each side, move the side with the smaller maxTrapping Rain Water
Ends of a string / expand around a centercompare outer characters moving in, or grow outward from a middle (odd and even centers)Valid Palindrome, Longest Palindromic Substring

The teacher's 4 array patterns

patternwhen she uses itMove Zeroes?
Two pointerswe work with a couple of particular elements, e.g. a zero and a non-zero to swapyes
Sliding windowcontinuous subarrays (their length, sum, …)no
Prefix sumsums of continuous subarraysno
Kadane'sbest continuous sum with negativesno

Her reasoning: the last three are all about continuous pieces of the array. Here we just want to grab a zero and a non-zero and swap them, which needs two pointers at two different places.


Part A · Brute force: collect the non-zeros in a new array

LeetCode 283

1The question in simple words

You get an integer array nums. Move all the 0s to the end, while keeping the non-zero numbers in their original relative order ("relative order" = who comes before whom). Do it in place: change nums itself, don't return a new array (the function returns nothing).

before010312
after131200correct: 1, 3, 12 in the same order
not this123100wrong: zeros at the end, but the order changed

Why sorting doesn't work

The teacher kills this idea first. Sorting ascending puts the zeros at the front (and negatives before them). To get zeros at the end you might try sorting descending, which gives [12, 3, 1, 0, 0]. The zeros are at the end, but 1, 3, 12 has become 12, 3, 1. The question forbids changing the order, so no sorting. We have to pick each element up and put it in the right place ourselves.

2What the constraints tell us

3Intuition: copy out the non-zeros, then fill with zeros

Walk through the array once. Every time you see a non-zero, append it to a new list. Because you visit them left to right, they land in the new list in the same order. Whatever is left over must be zeros, so pad the end with that many zeros.

4Building the logic from the example

[0, 1, 0, 3, 12]. Ask each element "are you non-zero?":

How many zeros to add?

We don't even need to count the zeros separately. The total length is 5 and we kept 3 non-zeros, so the number of zeros is 5 − 3 = 2. Append two zeros → [1, 3, 12, 0, 0].

Doubt: the question says "in place". Is returning the new list OK?
→ No. LeetCode checks the original nums list, so we must copy the result back into it. In Python nums[:] = result overwrites the contents of the same list object. (Plain nums = result would only rename a local variable and the caller would see no change.) Even then, it still used an extra array of size n, which is why the teacher moves on to a better approach.

5Approach steps

  1. Make an empty list result.
  2. For each number in nums: if it's not 0, append it.
  3. Append len(nums) − len(result) zeros.
  4. Copy result back into nums: nums[:] = result.

6Code (Python)

Brute force: O(n) time, but O(n) extra space
class Solution:
    def moveZeroes(self, nums):
        result = []
        for x in nums:
            if x != 0:                          # keep non-zeros, in order
                result.append(x)
        zeros = len(nums) - len(result)         # whatever is missing were zeros
        result.extend([0] * zeros)
        nums[:] = result                        # write back into the SAME list

7Code line by line

linewhat it means
result = []The extra array. This is what costs O(n) space.
for x in nums: if x != 0: result.append(x)Visit left to right, keep only non-zeros. Their order is preserved automatically.
zeros = len(nums) - len(result)Total minus non-zeros = number of zeros.
result.extend([0] * zeros)Put that many zeros at the end.
nums[:] = resultReplace the contents of the original list, so the change is in place.

8Dry run (hand table)

stepxnon-zero?result after
10no[]
21yes[1]
30no[1]
43yes[1, 3]
512yes[1, 3, 12]
endzeros = 5 − 3 = 2[1, 3, 12, 0, 0] → copied into nums ✓

9Complexity & remember

Remember the brute forceCollect non-zeros in order, add n − len(result) zeros, copy back with nums[:] =. Easy, but O(n) extra space.

Part B · Optimal: read pointer + write pointer, swap in place

LeetCode 283

1The question again, with the new goal

Same question. Now: one pass, no extra array (O(1) extra space).

2What the constraints tell us now

3Intuition: left keeps a seat for the next non-zero

Two pointers, both starting at index 0:

When right finds a zero, it does nothing and moves on. When it finds a non-zero, that number swaps into left's seat, and left moves to the next seat. Zeros get pushed backward one swap at a time.

4Building the logic from the example

[0, 1, 0, 3, 12], left = 0, right = 0.

right on a zero → just move on

right = 0 sees 0. A zero is already "in the wrong half", but there's nothing to put in its place yet. No swap. Only right moves. left stays at 0, still keeping that seat free.

right on a non-zero → swap into left's seat, then move left

right = 1 sees 1. Swap nums[1] with nums[left] = nums[0] → [1, 0, 0, 3, 12]. Now seat 0 is taken, so left moves to 1.

Doubt 1: why does left move only after a swap?
→ Because left means "the place for the next non-zero". After a swap, that place has just been filled. The next non-zero needs a new place, which is one step to the right. If right saw a zero, no seat was filled, so left stays.
Doubt 2: in the video, at index 1 she says the element is a zero and then swaps it. Is that right?
→ That's a slip of the tongue. The element at index 1 is 1, a non-zero, and that's why it swaps. Her rule (and her code) is clear: swap only when nums[right] != 0.

right = 2 sees 0 → skip. right = 3 sees 3 → swap with nums[1] → [1, 3, 0, 0, 12], left = 2. right = 4 sees 12 → swap with nums[2] → [1, 3, 12, 0, 0], left = 3. right runs off the end, the loop stops. Done.

Writing the swap: why not just put 0 at nums[right]?

The swap uses a temporary variable:

  1. temp = nums[right] (save the non-zero, we don't know its value in advance)
  2. nums[right] = nums[left]
  3. nums[left] = temp

Usually nums[left] is 0, so step 2 writes a 0. Why not write 0 directly? Because left and right can be the same index. Then the swap is "swap with itself", and writing 0 would erase a non-zero.

Doubt 3: when are left and right equal?
→ The teacher mentions the case of an array with one element. It's actually wider: left == right whenever no zero has been seen yet, e.g. at the start of [4, 5, 0, 6]. right = 0 sees 4 with left = 0 → swap 4 with itself. If step 2 were nums[right] = 0, we'd get [0, …] and lose the 4. Copying nums[left] instead is always correct.
Doubt 4: why is the order of the non-zeros kept?
→ right meets the non-zeros left to right, and each one goes into the next seat (0, 1, 2, …). So they're seated in the order they were met. Also, everything between left and right − 1 is always a zero, so a swap only ever moves a zero backward, never a non-zero past another non-zero.

The loop: right moves by itself

right moves on every step, zero or not, so a for right in range(n) loop handles it. left moves only on a swap, so it lives outside the loop (left = 0) and we do left += 1 by hand.

The question says "do not return anything", so the function just ends after the loop.

5Approach steps

  1. left = 0.
  2. For right from 0 to n − 1:
  3. If nums[right] != 0: swap nums[left] and nums[right], then left += 1.
  4. (If it's 0: do nothing, the loop moves right.)

6Code (Python)

Optimal: same-direction two pointers, O(n) time, O(1) space
class Solution:
    def moveZeroes(self, nums):
        n = len(nums)
        left = 0                            # seat for the next non-zero
        for right in range(n):              # right reads every element
            if nums[right] != 0:            # a non-zero: seat it
                temp = nums[right]
                nums[right] = nums[left]    # NOT "= 0": left may equal right
                nums[left] = temp
                left += 1                   # that seat is taken, move on
        # nothing to return: nums was changed in place

The Pythonic swap nums[left], nums[right] = nums[right], nums[left] does the same three steps in one line.

7Code line by line

linewhat it means
left = 0Outside the loop, because it doesn't move every step. The first non-zero belongs at index 0.
for right in range(n):right visits every index once. The for loop does right += 1 for us.
if nums[right] != 0:Only non-zeros need to move. Zeros are left where they are (they'll be pushed back by later swaps).
temp = nums[right]Remember the non-zero value before overwriting it.
nums[right] = nums[left]Put whatever is in the seat (a 0, or the same number if left == right) where the non-zero was.
nums[left] = tempThe non-zero takes its seat.
left += 1Next seat. Done by hand, because only a swap moves left.

8Dry run

nums = [0, 1, 0, 3, 12].

stepleftrightvalues at left, rightdecision (which pointer moves and why)array after
1000, 0nums[right] is 0 → no swap; only right moves[0, 1, 0, 3, 12]
2010, 1non-zero → swap seats 0 and 1; left → 1, right → 2[1, 0, 0, 3, 12]
3120, 0zero → no swap; only right moves[1, 0, 0, 3, 12]
4130, 3non-zero → swap 1 and 3; left → 2[1, 3, 0, 0, 12]
5240, 12non-zero → swap 2 and 4; left → 3[1, 3, 12, 0, 0]
end35–right is past the end → loop stops[1, 3, 12, 0, 0] ✓

The array at key moments (yellow = the pointer cells, grey = seats already filled with their final non-zero):

step 2010312right found 1 → swap into seat 0
LR   
step 4100312right found 3 → swap into seat 1 (the zeros sit between L and R)
 L R 
end131200left = 3: three non-zeros seated, zeros behind them
   L 

A case with self-swaps: [4, 5, 0, 6]. right = 0 and 1 swap with themselves (left == right), left becomes 2. right = 2 is 0 → skip. right = 3 swaps 6 into seat 2 → [4, 5, 6, 0] ✓.

9Complexity & remember

She submitted it and it passed (Java on screen, then the same logic in Python and C++).

Doubt 5: the follow-up asks to minimise operations. Does this already do that?
→ It does one pass with at most one swap per non-zero, which is good. A small extra trim (not in the video): skip the swap when left == right, since swapping a cell with itself changes nothing. Then an array with no zeros does zero writes.
Remember the optimalleft = 0; for right: if nums[right] != 0 → swap with nums[left], left += 1. left = "seat for the next non-zero". Swap, don't write 0.

Part C · Revision page

SortingBrute force (extra array)Two pointers (swap)
keeps order?noyesyes
in place?–copies back at the endyes
timeO(n log n)O(n)O(n), one pass
extra space–O(n)O(1)
Opposite-end pointersSame-direction pointers
start0 and n − 1both at 0
movestoward each otherboth forward; fast every step, slow only when something is kept
needs sorting?usuallyno
exampleTwo Sum II, 3SumMove Zeroes
If you remember only 5 lines 1. Don't sort: it breaks the order of the non-zeros.
2. right reads every element; left is the seat for the next non-zero.
3. Non-zero → swap into left's seat, left += 1. Zero → do nothing.
4. Swap (don't write 0), because left can equal right.
5. One pass, O(n) time, O(1) space, returns nothing.
Mistakes to avoid ✗ sorting (ascending or descending)
✗ nums[right] = 0 instead of a real swap (erases numbers when left == right)
✗ moving left on every step
✗ opposite-end swapping (scrambles the order)
✗ nums = result instead of nums[:] = result in the brute force
test it yourself (paste under either Solution above)
s = Solution()
for nums in ([0, 1, 0, 3, 12], [0], [4, 5, 0, 6], [0, 0, 1], [1, 2, 3], [-1, 0, -2]):
    s.moveZeroes(nums)
    print(nums)
# [1, 3, 12, 0, 0]
# [0]
# [4, 5, 6, 0]
# [1, 0, 0]
# [1, 2, 3]
# [-1, -2, 0]

Based on this video: Move Zeroes | Two Pointers