All topics
10

Rearranging an array without a second one

Tier 1Free

Where it runs

1. The problem

You get a big array (a numbered list of boxes) and you have to rearrange it. You might reverse it, push all the zeros to the end, or take some items out. The easy way is to make a new array and fill it. That works, but it uses twice the memory: for a while the old array and the new one both sit in memory.

Sometimes that is fine. Sometimes the array is huge, or the program reuses it thousands of times a second, and there is no memory for a second copy. Then you change the array you already have and use only a couple of extra variables. That is an in-place change: you edit the same array and never make a new one.

2. In plain words

Take the first test, [0, 1, 0, 3]. The answer has to be [1, 3, 0, 0], and you may not build a second array to get there.

So you work inside the same four boxes. One marker, write, remembers which box gets the next number you keep. It starts at box 0. A second marker, read, looks at the boxes one by one from the left. When read finds a number that is not 0, you copy it into box write and move write one box to the right. The 1 lands in box 0, the 3 lands in box 1. Boxes 2 and 3 are then set to 0. The same four boxes now hold [1, 3, 0, 0], and no extra memory was needed. Saving that memory is the reason for the whole lesson.

3. The idea

"In place" means you edit the original boxes directly and spend almost no extra memory. Two moves solve most of these problems. Both use a pointer: a variable that holds a box number.

Two pointers from the ends (to reverse an array or swap items). Pointer lo starts at the first box, pointer hi at the last. Swap the two boxes they point at. Then move each one a step toward the other. Stop when they meet.

A write pointer (to move or remove items). A slow pointer write remembers where the next kept item goes. A fast pointer read walks the whole array. Each time read finds an item to keep, copy it into box write and move write one step forward.

0 1 0 3 skip keep →write skip keep →write then fill the rest with 0 → [1, 3, 0, 0]

Trace "zeros to the end" on [0, 1, 0, 3]. Read 0: skip it. Read 1: keep it, write it into box 0. The array is now [1, 1, 0, 3] and write is 1. Read 0: skip it. Read 3: keep it, write it into box 1. The array is now [1, 3, 0, 3] and write is 2. The kept numbers already stand at the front. Boxes 2 and 3 still hold old values, so fill them with zeros: [1, 3, 0, 0]. Done, with no second array.

4. What it costs

Time is O(n): one walk over the array. Space is O(1): a couple of variables for the indices and one spare box for a swap. None of these grow with the array. A new array would cost O(n) extra memory, and here you may not spend it.

One catch in JavaScript: you cannot change a string in place. So these problems give you an array of characters instead (one letter per box). You edit the array, and the string stays as it was.

5. Where this shows up

Before a program saves its log (the file where it writes down events), it removes private data from it. It does that without copying a big buffer (a stretch of memory that holds data). An input buffer gets cleaned up in place. A message gets packed into a network buffer of fixed size. A ring buffer gets maintained: a buffer of fixed size that fills up in a circle. In all of these, memory is tight or the same buffer is reused for every request. A fresh copy is wasted work there, and the write pointer lets you skip it.

Remove every zero in place
inPlaceTransform([0, 4, 0, 0, 7, 2, 0, 5]) // shouldKeep = x => x !== 0
arr
0
4
0
0
7
2
0
5

write
read = -write = 0

Remove every 0 without a second array. read looks at each slot, write marks where the next kept value goes.

Speed race
Moving the zeros to the end of the same 40 numbers by shifting and by one write pointer

Bars: the numbers; a 0 has no bar. Both lanes keep the other numbers in their order.

One operation is one comparison or one write. Every frame, each lane does the same number of operations, so the slow one falls behind.

Shift on every zero O(n²)
0 operations
Write pointer O(n)
0 operations

 

And with more numbers

Operations for n numbers, and roughly how long they take at 100 million simple operations a second. An estimate from the same count, not a measurement.

n = 1,000
Shift on every zero188.5K · 1.9 ms
Write pointer2K · under 1 ms
n = 100,000
Shift on every zero1.9B · 19 s
Write pointer200K · 2 ms
n = 10,000,000
Shift on every zero18.8T · 2.2 days
Write pointer20M · 200 ms

Recognise it

In-place array transform

Say these words and it is this pattern

  • modify the array in place
  • O(1) extra space
  • do not allocate another array/string
  • reverse/rotate/partition/move elements over a single buffer
  • return the new length after removing duplicates

Looks similar, isn't

  • Two pointers (same direction)

    A slow and fast pair is one mechanic that rewrites a buffer. Working in place is the goal, not the mechanic.

  • Hash set / map

    A set drops duplicates in one pass but stores every key. In-place work exists to avoid that memory.

  • Elementary sorts (selection, bubble, insertion)

    Insertion sort also rewrites one buffer, but it reorders values. Here the surviving order never changes.

  • Prefix sums

    Prefix sums build a second array of running totals. That second array is the thing being avoided.

n up to 1e6 with a single pass gives O(n) time. Space stays O(1) because nothing new is allocated.

On LeetCode

Problems that ask you to rearrange, filter or reverse an array in place, with O(1) extra memory. Watch for the words "in-place" and "do not allocate extra space" in the statement.

Your task (Tier 1)

Write the function moveZeroes(nums).

Move every 0 to the end of the list. Keep the other numbers in the order they had. Do not make a new list. Change this one.

  • Input: nums, a list of numbers. Example: [0, 1, 0, 3].
  • Return: nothing. You only change nums.

Steps:

  1. Make a counter write = 0. It is the next free spot at the front of the list.
  2. Go through the list from left to right. When a number is not 0, copy it to nums[write], then add 1 to write.
  3. At the end, put 0 in every spot from write to the end of the list.

Done when all tests pass:

  • [0, 1, 0, 3] -> [1, 3, 0, 0]
  • [1, 2, 3] -> [1, 2, 3] (no zeros, nothing changes)
  • [0, 0] -> [0, 0] (all zeros)
  • [0] -> [0] (one number)
  • [] -> [] (empty list, must not crash)

Practice

moveZeroes (or reverseInPlace)

moveZeroes(nums) -> void (mutates nums)

export function moveZeroes(nums) {
  // Your code here. The task is described in the box above the editor.
}

Running as a guest

The tests run right here, no account needed. Your code is kept in this browser only. A free account keeps it on every device, marks the step done and shows where you are in the course.

Hint ladder

Stuck? Reveal one rung at a time, but try it yourself first.

    We use necessary cookies to run the site (sign-in and language). If you accept, we also load Google Analytics to see which pages are used, and Google reCAPTCHA to keep spam off the contact and bug-report forms. Privacy policy