Where it runs
1. The problem
You have a sorted list where some values repeat: [1, 1, 2, 3, 3]. You want each value once: [1, 2, 3]. Same order, no repeats.
The simplest way is to make a new list and copy the values you keep into it. That works, but it takes twice the memory: for a while both lists sit in memory at once. Sometimes the list is huge. Sometimes it is a buffer (a stretch of memory that holds data) that the program reuses thousands of times a second. Then the second copy may cost more memory than you have.
The sorted order helps. In a sorted list, equal values always stand next to each other. So you can rewrite the list you already have and spend almost no extra memory.
2. In plain words
In a sorted list, repeats stand next to each other. You want one copy of each value, written into the same list.
Walk the list from left to right and look at every number. A number is new when it differs from the number before it. Copy each new number to the front of the list, into the first free place. Skip each repeat.
On [1, 1, 2, 3, 3] it goes like this. Keep the first 1. Skip the second 1. Write 2 into the 2nd place and 3 into the 3rd. Skip the last 3. The list now starts with 1, 2, 3, and you never needed a second list.
3. The idea
Take two markers. Both move the same way, at different speeds. The fast read marker (read) looks at every element. The slow write marker (write) remembers where the next kept value goes.
The list is sorted, so a value is new exactly when it differs from its left neighbour. So read compares the current value with the element before it. If the value is new, copy it into the write cell and move write one step forward. If it is a repeat, skip it: only read moves on.
In lesson 19 the two markers closed in from opposite ends. Here both move from left to right. The slow marker shows where to write, and it does not limit the search.
Trace it on [1, 1, 2, 3, 3]. Start with write = 1: the first element always stays.
- read=1:
1 == 1, a repeat, skip it. - read=2:
2 != 1, write 2 into cell 1, andwritebecomes 2. The array is now[1, 2, 2, 3, 3]. - read=3:
3 != 2, write 3 into cell 2, andwritebecomes 3. The array is now[1, 2, 3, 3, 3]. - read=4:
3 == 3, a repeat, skip it.
The first 3 cells are [1, 2, 3], and the new length is 3. Cells 3 and 4 still hold old values. Nobody checks them.
4. What it costs
Time is O(n): read visits each element once, and write only moves forward. Memory is O(1): the list is rewritten inside itself, with no extra structure.
You could put every kept value into a hash set (the Set from the lesson on sets) and build the list from it. That is also O(n) time, but it takes O(n) memory. On sorted input that memory is wasted: the comparison with the previous element tells you everything the set would.
5. Where this shows up
Analytics systems get events from at-least-once delivery. That kind of delivery guarantees an event arrives, but it sometimes sends the same event twice. Before passing a sorted buffer of events on, the system removes the repeats in place. Databases throw away rows marked for deletion during compaction (a pass that rewrites stored data more tightly). The uniq command removes repeats from a sorted stream of lines. Wherever a sorted buffer has to shrink without a second buffer, a write pointer does the job. Often this is on a hot path, that is, code that runs very often.
compact([1, 1, 2, 2, 2, 3, 4]) // keep x when x !== arr[slow - 1]slow
Remove repeats from a sorted array in place. fast reads every value, slow marks the next free slot for a value we keep.
Bars: the sorted numbers, most of them repeated. Both lanes keep one of each at the front and fill the freed tail with 0.
One operation is one comparison or one write. Every frame, each lane does the same number of operations, so the slow one falls behind.
O(n²)O(n)
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.
Recognise it
Say these words and it is this pattern
- remove duplicates in place, keep order
- move/partition elements while preserving order
- compact an array and use O(1) extra space
- shift zeros or a target value to the end
Looks similar, isn't
- In-place array transform
Working in one buffer is the goal. A trailing write pointer is one way to reach it.
- Two pointers (opposite ends)
Those pointers start at the ends of sorted data and converge. These both start at index zero.
- Sliding window (variable)
A window also moves two indices forward, but it keeps a live span. Here the trailing index is a write slot.
- Fast & slow pointers
There the fast pointer takes two steps to catch a cycle. Here it takes one and reads.
n up to 1e6 gives O(n) time and O(1) space. The read pointer sees each element once.
On LeetCode
Problems on a sorted array or a string where you remove repeats or unwanted items in place and return the new length. A slow write pointer and a fast read pointer move in the same direction.
This is the lesson task, so keep a value only when it differs from the one before it.
Allow each value twice by comparing it with the value two places back in the written part.
One pointer walks the short string, the other walks the long one, and both only move forward.
Read a run of equal characters, then write the character and the run's length at the write pointer.
Your task (Tier 1)
Write the function dedupeSorted(nums).
The list is sorted, so equal numbers stand next to each other. Move the different numbers to the front of the list, in order, and return how many there are.
- Input:
nums, a list of numbers sorted from low to high. Example:[1, 1, 2, 3, 3]. - Effect: the different numbers move to the front of
nums, in order. - Return: how many different numbers there are (the new length). The tests check only the front part. They ignore whatever is left after it.
Steps:
- If the list is empty, return
0. - Make a counter
write = 1. The number at position 0 is already in place. - Go through the list from position 1. When a number differs from the one right before it, copy it to
nums[write]and add 1 towrite. - Return
write.
Done when all tests pass:
[1, 1, 2, 3, 3]->3(the front becomes1, 2, 3)[1, 2, 3]->3(no repeats, nothing changes)[5, 5, 5, 5]->1(all the same, only the first stays)[7]->1(one number)[]->0(empty list, must not crash)
Practice
dedupeSorted
dedupeSorted(nums) -> number (new length; nums mutated in place so nums[0..len-1] is deduped)
export function dedupeSorted(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.