LeetCode #27 — Easy

Remove Element

A visual guide to removing all occurrences of a value in-place using the two-pointer technique.

Patterns Used

Roadmap

  1. Brute Force — Shift Elements
  2. The Core Insight — Two Pointers
  3. Visual Walkthrough
  4. Edge Cases
  5. Full Annotated Code
  6. Interactive Demo
  7. Complexity Analysis
Step 01

Brute Force — Shift Elements

The naive approach: when you find the target value, shift every element after it one position to the left. This fills the gap but requires moving many elements repeatedly.

Shift & Shrink

input
3
2
2
3
val = 3
↓ find 3 at index 0, shift left ↓
shift 1
2
2
3
_
↓ find 3 at index 2, shift left ↓
result
2
2
_
_
k = 2

This works but each removal causes an O(n) shift, making the worst case O(n²). We can do much better.

// Brute force: O(n^2) time, O(1) space
int n = nums.length;
for (int i = 0; i < n; ) {
    if (nums[i] == val) {
        for (int j = i; j < n - 1; j++)
            nums[j] = nums[j + 1]; // shift left
        n--;
    } else {
        i++;
    }
}
Step 02

The Core Insight — Two Pointers

Instead of shifting elements, use two pointers: slow tracks where to write, fast reads every element. When fast sees a non-target value, copy it to the slow position.

The Two-Pointer Pattern

Both pointers start at index 0. The fast pointer checks each element. If it is not the target, we write it at the slow position and advance both. If it is the target, only fast advances.

slow pointer
Write position. Everything at indices 0..slow-1 are kept values.
fast pointer
Read position. Scans every element, skipping the target value.
šŸ’”

Key difference from #26: In Remove Duplicates, we compare nums[fast] vs nums[slow]. Here, we compare nums[fast] vs val. Also, both pointers start at 0 (not slow=0, fast=1) because the first element could be the target.

Step 03

Visual Walkthrough

Let's trace through [3, 2, 2, 3] with val = 3:

fast=0: nums[0]=3 is the target

S/F
nums
3
2
2
3

3 == val → skip! Only fast advances.

fast=1: nums[1]=2 is NOT the target

S
F
nums
3
2
2
3

2 != 3 → copy! nums[0] = 2, then slow++.

fast=2: nums[2]=2 is NOT the target

S
F
nums
2
2
2
3

2 != 3 → copy! nums[1] = 2, then slow++.

fast=3: nums[3]=3 is the target

S
F
nums
2
2
2
3

3 == val → skip!

Final Result

nums
2
2
_
_
return slow = 2
Step 04

Edge Cases

Empty Array

[ ]

Loop never runs, slow stays at 0. Return 0.

All Elements Are the Target

input
3
3
3
result
_
_
_

val=3. Fast always skips. Slow never advances. Return 0.

No Elements Are the Target

input
1
2
4
5
result
1
2
4
5

val=3. Every element is copied. Slow reaches 4. Array is unchanged.

Single Element

[3], val=3
3
return 0
[1], val=3
1
return 1
Step 05

Full Annotated Code

class Solution {
    public int removeElement(int[] nums, int val) {

        // slow = write position for kept elements
        int slow = 0;

        // fast reads every element in the array
        for (int fast = 0; fast < nums.length; fast++) {

            // Only keep elements that are NOT the target
            if (nums[fast] != val) {
                nums[slow] = nums[fast]; // copy to write position
                slow++;                  // advance write position
            }
            // If nums[fast] == val, just skip it
        }

        // slow is the count of non-val elements
        return slow;
    }
}
⚔

Subtle difference from #26: Here slow starts at 0 and represents "how many elements we've kept" (the next write index). In #26, slow starts at 0 and represents "index of the last unique element." That's why #26 returns slow + 1 but this returns just slow.

Step 06

Interactive Demo

Enter an array and a target value, then step through the algorithm to see the slow and fast pointers in action.

āš™ Two-Pointer Visualizer



Step 07

Complexity Analysis

Time
O(n)
Space
O(1)

The fast pointer visits each element exactly once, giving us O(n) time. We use only a constant number of variables for O(1) extra space. A massive improvement over the O(n²) brute force.

šŸ“

Pattern recognition: Problems #26 and #27 both use the same "slow/fast read-write pointer" pattern. The only difference is the condition: #26 checks nums[fast] != nums[slow] (new unique), while #27 checks nums[fast] != val (not the target). Recognizing this shared pattern is key to solving similar in-place array problems quickly.