LeetCode #26 — Easy

Remove Duplicates from
Sorted Array

A visual walkthrough of the two-pointer technique for in-place deduplication — from brute force to optimal.

Solve on LeetCode
The Problem

Problem Statement

Given an integer array nums sorted in non-decreasing order, remove the duplicates in-place such that each unique element appears only once. The relative order of the elements should be kept the same.

Consider the number of unique elements in nums to be k​​​​​​​​​​​​​​. After removing duplicates, return the number of unique elements k.

The first k elements of nums should contain the unique numbers in sorted order. The remaining elements beyond index k - 1 can be ignored.

Custom Judge:

The judge will test your solution with the following code:

int[] nums = [...]; // Input array
int[] expectedNums = [...]; // The expected answer with correct length

int k = removeDuplicates(nums); // Calls your implementation

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
    assert nums[i] == expectedNums[i];
}

If all assertions pass, then your solution will be accepted.

Example 1:

Input: nums = [1,1,2]
Output: 2, nums = [1,2,_]
Explanation: Your function should return k = 2, with the first two elements of nums being 1 and 2 respectively.
It does not matter what you leave beyond the returned k (hence they are underscores).

Example 2:

Input: nums = [0,0,1,1,1,2,2,3,3,4]
Output: 5, nums = [0,1,2,3,4,_,_,_,_,_]
Explanation: Your function should return k = 5, with the first five elements of nums being 0, 1, 2, 3, and 4 respectively.
It does not matter what you leave beyond the returned k (hence they are underscores).

Constraints:

  • 1 <= nums.length <= 3 * 104
  • -100 <= nums[i] <= 100
  • nums is sorted in non-decreasing order.
Patterns Used

Roadmap

  1. Brute Force — Copy to New Array
  2. The Core Insight — Two Pointers, Same Direction
  3. Visual Walkthrough
  4. Edge Cases
  5. Full Annotated Code
  6. Interactive Demo
  7. Complexity Analysis
Step 01

Brute Force — Copy to New Array

The simplest approach: iterate through the sorted array, copy each unique element into a new array, then copy back. Let's see it visually:

Copy & Overwrite

input
0
0
1
1
1
2
2
3
↓ copy unique values ↓
temp[]
0
1
2
3
↓ copy back ↓
result
0
1
2
3
1
2
2
3
k = 4

This works but uses O(n) extra space for the temporary array. The problem says we must do it in-place with O(1) extra memory. We need a smarter approach.

// Brute force: O(n) time, O(n) space
int[] temp = new int[nums.length];
int k = 0;
for (int i = 0; i < nums.length; i++) {
    if (i == 0 || nums[i] != nums[i - 1])
        temp[k++] = nums[i];
}
System.arraycopy(temp, 0, nums, 0, k);
Step 02

The Core Insight — Two Pointers, Same Direction

Since the array is sorted, all duplicates are adjacent. We can use two pointers moving in the same direction:

The Two-Pointer Pattern

The slow pointer marks the boundary of the "processed unique" region. The fast pointer scans ahead looking for new unique values.

slow pointer
Points to the last unique element placed. Everything at indices 0..slow is unique.
fast pointer
Scans through every element. When it finds a new value, it copies it to slow + 1.
💡

Key idea: When nums[fast] != nums[slow], we found a new unique value. Increment slow, then copy the value. This overwrites duplicates in-place without needing extra space.

Step 03

Visual Walkthrough

Let's trace through [1, 1, 2] step by step:

Initial State

S F
nums
1
1
2

slow=0, fast=1. nums[1] == nums[0] (both 1) → duplicate, skip. fast++.

Fast finds a new value

S F
nums
1
1
2

slow=0, fast=2. nums[2] != nums[0] (2 != 1) → new unique! Increment slow to 1, copy: nums[1] = 2.

Final Result

S
nums
1
2
_
return slow + 1 = 2
Step 04

Edge Cases

Empty Array

[ ]

Length is 0 → return 0 immediately. The guard clause handles this.

Single Element

5

Only one element, already unique. The loop never runs. Return slow + 1 = 1.

All Same Elements

input
3
3
3
3
result
3
_
_
_

Fast never finds a different value. Slow stays at 0. Return 1.

All Unique Elements

input
1
2
3
4
result
1
2
3
4

Every element is unique. Each copy overwrites the same position. Array is unchanged, return 4.

Step 05

Full Annotated Code

class Solution {
    public int removeDuplicates(int[] nums) {

        // Guard: empty array
        if (nums.length == 0) return 0;

        // slow points to the last unique element
        int slow = 0;

        // fast scans every element starting from index 1
        for (int fast = 1; fast < nums.length; fast++) {

            // Found a new unique value?
            if (nums[fast] != nums[slow]) {
                slow++;                  // advance the write position
                nums[slow] = nums[fast]; // place the unique value
            }
            // If equal, fast just moves on (skip duplicate)
        }

        // slow is the index of the last unique element
        // The count of unique elements is slow + 1
        return slow + 1;
    }
}
Step 06

Interactive Demo

Enter a sorted array and step through the two-pointer algorithm. Watch the slow and fast pointers move in real time.

⚙ Two-Pointer Visualizer


Step 07

Complexity Analysis

Time
O(n)
Space
O(1)

The fast pointer visits each element exactly once, giving O(n) time. The slow pointer advances only when a new unique value is found, so it performs at most n writes. We use only two integer variables for O(1) extra space -- no temporary array needed.

Approach Breakdown

BRUTE FORCE
O(n) time
O(n) space

A single pass copies unique elements into a temporary array, then a second pass copies them back. Both passes are O(n), but the temp array requires O(n) extra memory proportional to the number of unique elements.

TWO POINTERS
O(n) time
O(1) space

The fast pointer reads each of the n elements exactly once. When a new unique value is found, it is written at the slow position -- at most n writes total. Only two integer index variables are needed, giving true O(1) auxiliary space.

🎯

The slow pointer tracks the "write position" -- every unique element is written exactly once. Because the array is sorted, all duplicates are contiguous. The fast pointer simply skips over them while slow holds the boundary. This is the canonical "read/write pointer" pattern for in-place array problems, and O(n) time is inherently optimal since every element must be inspected at least once.

Coach Notes

Common Mistakes

Review these before coding to avoid predictable interview regressions.

Moving both pointers on every comparison

Wrong move: Advancing both pointers shrinks the search space too aggressively and skips candidates.

Usually fails on: A valid pair can be skipped when only one side should move.

Fix: Move exactly one pointer per decision branch based on invariant.