Start learning free

LESSON 202 OF 348 · SORTING

Remove Duplicates from Array in TypeScript: O(n log n) In Place

Practice O(n log n) Read the full explanation

Given an array of integers, remove the duplicates in-place such that each unique element appears only once.

Do not allocate extra space for another array. You must do this by modifying the input array in-place with O(1) extra memory.

The resulting array can be in any order.

Example:

Input: [2, 3, 1, 1, 4, 3, -2, 1]
Output: [2, 3, 1, 4, -2]
			

Note:

Your algorithm should run in O(n log n) time and use O(1) extra space.


Hints:

Let's sort the array in non-decreasing order. In this way all the equal elements will come one after another. Also, let's remove the O(1) extra space constraint for now to make the problem easier. Let's use an extra array uniqueVals and push the unique values from nums there.

Then, we can traverse the sorted array nums from left to right using an index i. We should push nums[i] into uniqueVals only if that value has not been seen before. How can we check this?

nums[i] has never been seen before if nums[i] != nums[i - 1].

It's time to solve the question with the original constriant. We will still traverse sorted nums but each time we find a new unique element, we will overwrite an element of nums with this value, starting from the left:
  • The first unique value will be stored in nums[0]
  • The second unique value will be stored in nums[1]
  • ...
How can we achieve this?

We will use an index variable p, initially equal to 0, to keep track of where we should place the next unique value we find.

We will traverse nums and check if nums[i] != nums[i - 1]. If so, we will set nums[p] to nums[i] and increment p. What should we return when we finish the traversal?

We should return the subarray nums[0...p].

STEP 1 OF 6
    Ctrl + Enter to check

    TYPESCRIPT · SORTING

    Remove Duplicates From Array in TypeScript, explained

    Problem overview

    The method removeDuplicates receives an array of integers, nums, and must return an array in which every value appears once. The order may be anything. The statement asks for an in-place solution: no second array, only O(1) extra memory, and an O(n log n) running time.

    In the example, [2, 3, 1, 1, 4, 3, -2, 1] holds five different values: 2, 3, 1, 4 and -2. Any ordering of those five numbers is a correct answer.

    Approach: sort, then compact the array

    Equal values are hard to find in an unsorted array, but easy in a sorted one: they sit next to each other. After sorting in ascending order, a value is new exactly when it differs from the element before it.

    The first idea is to push each new value into a second array. That works, but it uses O(n) extra space. The in-place version uses the front of nums itself as the output. A write index p marks where the last distinct value was placed. Each time a new value appears, p moves one step and the value is copied there. The write position never passes the read position, so nothing unread gets overwritten.

    Take the sorted array [-2, 1, 1, 1, 2, 3, 3, 4]. Index 0 holds -2 and stays. At index 1 the value 1 is new, so p becomes 1 and the array stays as is. At index 4 the value 2 is new, so p becomes 2 and nums[2] becomes 2, giving [-2, 1, 2, 1, 2, 3, 3, 4]. The value 3 at index 5 goes to position 3, and 4 at index 7 goes to position 4. The first five elements are now -2, 1, 2, 3, 4.

    A Set would also remove duplicates in one line, [...new Set(nums)], and it keeps the first-seen order. It takes O(n) time but also O(n) extra space, so it breaks the in-place rule. Sorting trades some time for the space guarantee.

    Why is it safe to overwrite? When a write happens, p is at most i, so it lands on the position being read or on one already read. Values that haven't been read yet are never touched.

    Step by step

    1. Sort nums with (a, b) => a - b so equal values become neighbours. The comparator keeps the sort numeric; without it, 10 would sort before 9.
    2. Declare p as 0. It is the index of the last distinct value placed. The first element is always kept, so it is already in place.
    3. Loop from i = 1 over the rest of the array.
    4. Check for a new value: nums[i] !== nums[i - 1]. Because the array is sorted, a difference from the previous element means a first occurrence.
    5. Move p forward and copy: p++ first, then nums[p] = nums[i]. The value lands right after the distinct values found so far.
    6. Return the first p + 1 elements with slice(0, p + 1). Index p is the last valid one, so the length is p + 1.

    The solution in TypeScript

    class Solution {
    
        removeDuplicates(nums: number[]): number[] {
            nums.sort((a, b) => a - b);
            let p = 0;
            for (let i = 1; i < nums.length; i++) {
                if (nums[i] !== nums[i - 1]) {
                    p++;
                    nums[p] = nums[i];
                }
            }
            return nums.slice(0, p + 1);
        }
    }
    

    Details worth knowing:

    • The array keeps its length. After the loop, nums still has its original length, with leftover values after position p. In the example, the array ends as [-2, 1, 2, 3, 4, 3, 3, 4]. Only the first p + 1 elements are the answer.
    • slice returns a new array. The returned array is separate from nums. It holds at most n values, and it is the output, so the lesson doesn't count it as extra space. Setting nums.length = p + 1 would truncate the array itself instead.
    • !==. It compares without converting types, so it is the safe default in TypeScript. For numbers, it matches what != would do here.
    • Sorting changes the order. The result is in ascending order, not in the order the values first appeared. The statement allows any order.

    Complexity

    Time: O(n log n). The sort takes O(n log n). The loop makes one pass with constant work per element. The final slice copies at most n values.

    Space: O(1) besides the returned array. The compaction uses only p and i, and the sort works in place.

    Edge cases and common mistakes

    • An empty array. The loop doesn't run, and slice(0, 1) of an empty array is empty, so the result is [].
    • One element. [5] stays [5].
    • All values equal. [7, 7, 7] gives [7], because p never moves.
    • Returning slice(0, p). That drops the last distinct value, since p is an index and not a count.
    • Copying before moving p. Writing nums[p] = nums[i] before p++ overwrites the last distinct value instead of adding a new one.
    • Skipping the comparator. Without it, the array is ordered as strings. Equal numbers still stay together, but the result's order is surprising: [10, 9, 9, 100] would come back as [10, 100, 9]. With the comparator it is [9, 10, 100].

    Testing

    • [2, 3, 1, 1, 4, 3, -2, 1] gives [-2, 1, 2, 3, 4], which has the same values as the statement's example in a different order.
    • [10, 9, 9, 100] gives [9, 10, 100].
    • [7, 7, 7] gives [7].
    • [] gives [].
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free