Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window
P1EasyMental model~4 min speedrun

Read/Write Pointers (In-Place Array Modification, Dutch National Flag)

Grows into
Cyclic Sort
Unlocks
Quickselect
The problem

Remove Duplicates from Sorted Array (LC 26)(opens leetcode.com)

You're given an already-sorted (smallest to largest) integer array nums. Rewrite it in-place so every value appears just once, keeping the surviving values in their original relative order.

Say k is the count of distinct values in nums. Once the duplicates are gone, hand back that count k, and make sure the first k slots of nums hold those distinct values in sorted order — whatever sits past index k - 1 is never inspected.

Custom Judge:

We compare the k you return with the length of the expected trimmed array, then check that each of the first k slots of nums holds the expected value for that position. Anything after slot k - 1 is ignored.

Example 1
Input:nums = [1,1,2]
Output:2, nums = [1,2,_]
k comes back as 2, with slots 0 and 1 of nums holding 1 and 2 in order. Whatever is left past index k-1 (shown as an underscore) is never checked.

Read-Write Pointers

reader -> writer -> (Compaction Frontier)
Spot it when: You must remove, deduplicate or move elements in place and return the new length (or leave the array partitioned).
Read-Write Pointers
Python
1from typing import Callable, List, TypeVar
2 
3Item = TypeVar("Item")
4 
5 
6def read_and_write(items: List[Item], keep: Callable[[Item, int], bool]) -> int:
7 """Read-Write Pointers: move every kept item to the front of items, in order, in place.
8 keep(value, writer) returns True to keep value. writer is the number of items kept so far.
9 items[:writer] holds those items, so keep may compare value with items[writer - 1].
10 Precondition: keep reads only value and items[:writer], never a slot at writer or later.
11 Returns writer. The slots from writer on still hold old values. Move Zeroes writes its zeros there afterwards.
12 N = len(items). T = the cost of one keep call.
13 Time: O(N * T) | Space: O(1)
14 """
15 writer = 0 # items[:writer] holds the kept items, in order
16 # 1. Read Every Item, Write Only The Kept Ones:
17 # writer never gets ahead of reader, so items[writer] = items[reader] always lands on a slot whose old value has already been read, never one still waiting to be read.
18 for reader in range(len(items)):
19 if keep(items[reader], writer):
20 items[writer] = items[reader]
21 writer += 1
22 
23 return writer # The number of kept items
Invariant: nums[0:writer] holds, in order, exactly the kept elements among nums[0:reader].

writer marks where the next kept element goes; everything before it is final. reader visits every element and copies it to writer only when it should be kept.

Since writer never passes reader, a write never lands on an element that still has to be read, so the array can be rewritten in place. For sorted deduplication, start both at 1, because nums[0] is always kept.

The trap is comparing with the wrong slot: check nums[reader] against nums[writer - 1], the last element kept, not nums[writer], the next free slot, which still holds an old value; on LeetCode Example 2 of Remove Duplicates from Sorted Array (LC 26), [0,0,1,1,1,2,2,3,3,4], the wrong slot keeps 7 values instead of 5.

See it run

Read-Write Pointers, step by step

reader 1 · writer 1

reader 1 · writer 1

reader 2 · writer 1

reader 3 · writer 2

reader 4 · writer 2

reader 5 · writer 2

reader 6 · writer 3

reader 7 · writer 3

reader 8 · writer 4

reader 9 · writer 4

reader 10 · writer 5 · answer 5

Each box is one entry of nums, with its index above it. Copies are made inside the same array.

  • kept a final value before writer
  • writer the next free slot
  • reader the entry being read
  • reader, writer reader and writer on the same entry

Start: nums[0] is always kept, so writer = 1 and reader = 1. Everything before writer is final.

nums[1] = 0 equals nums[writer - 1] = 0, so skip it. writer stays at 1.

nums[2] = 1 differs from nums[writer - 1] = nums[0] = 0, so copy it to nums[1]. Then writer becomes 2.

nums[3] = 1 equals nums[writer - 1] = 1, so skip it. writer stays at 2.

nums[4] = 1 equals nums[writer - 1] = 1, so skip it. writer stays at 2.

nums[5] = 2 differs from nums[writer - 1] = nums[1] = 1, so copy it to nums[2]. Then writer becomes 3.

nums[6] = 2 equals nums[writer - 1] = 2, so skip it. The free slot nums[3] still holds the old value 1, so comparing with it would keep a repeat.

nums[7] = 3 differs from nums[writer - 1] = nums[2] = 2, so copy it to nums[3]. Then writer becomes 4.

nums[8] = 3 equals nums[writer - 1] = 3, so skip it. writer stays at 4.

nums[9] = 4 differs from nums[writer - 1] = nums[3] = 3, so copy it to nums[4]. Then writer becomes 5.

Done: reader passed the end, so return writer = 5. nums[0..4] holds the kept values in order.

00kept
10reader, writer
21
31
41
52
62
73
83
94
1 / 11
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Comparing with items[writer] instead of items[writer - 1]

    Mistake
    if nums[reader] != nums[writer]:
    Fix
    if nums[reader] != nums[writer - 1]:

    What happens: nums[writer - 1] is the last value kept. nums[writer] is the next free slot. It still holds an old value from the input. Comparing with that old value can keep a duplicate. It can also drop a value that is new.

    Example: Remove Duplicates from Sorted Array (LC 26), Example 2: nums = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4]
    Mistake: returns 7Fix: returns 5
  2. 2

    Gotcha 2: Comparing with items[reader - 2] instead of items[writer - 2]

    Mistake
    if nums[reader] != nums[reader - 2]:
    Fix
    if nums[reader] != nums[writer - 2]:

    What happens: Remove Duplicates from Sorted Array II (LC 80) keeps at most two copies of each value. A value is kept when it differs from nums[writer - 2], the second-to-last value kept. nums[reader - 2] may already hold a value that the writer copied there. Then the check compares with the wrong value and drops a copy that should stay.

  3. 3

    Gotcha 3: Leaving old values after writer in Move Zeroes (LC 283)

    Mistake
    nums[writer] = nums[reader]
    Fix
    nums[writer], nums[reader] = nums[reader], nums[writer]

    What happens: Move Zeroes (LC 283) must end with every zero after the non-zero values. Copying nums[reader] to nums[writer] leaves the old values after writer in place. Swap the two values instead. You can also write zeros from writer to the end after the loop. Either way, the non-zero values keep their order.

  4. 4

    Gotcha 4: Moving mid after a swap with high

    Mistake
    nums[mid], nums[high] = nums[high], nums[mid]
    high -= 1
    mid += 1
    Fix
    nums[mid], nums[high] = nums[high], nums[mid]
    high -= 1

    What happens: Sort Colors (LC 75) uses three pointers: low, mid and high. When nums[mid] is 2, it is swapped with nums[high], and high moves left. The value that arrives at mid has not been checked yet. Moving mid past it can leave a 0 or a 2 in the middle.

Not this one if…
  • You compare or pair elements from the two opposite ends, as in Valid Palindrome (LC 125), instead of keeping or dropping each element in order. See Converging Pointers
  • You need one value at a given rank, as in Kth Largest Element in an Array (LC 215), not the array compacted or split by a fixed rule. See Quickselect

Theory

The question we build

What we know

nums is sorted in ascending order. Equal values are always adjacent, so a value only needs to be compared with the last kept value to know whether it is a duplicate.

What the problem gives us

The input is nums, modified in place. Return the number of unique values, with that many unique values occupying the front of nums, in order.

The question we build

For each position reader, we ask: "does nums[reader] equal the last value already kept?" If it is new, where should it go? It goes right after the last kept value, at the first free slot.

Narrowing down to the template

Is nums[reader] kept, and where does it go? The first free slot is writer, and writer never gets ahead of reader, so a write never overwrites a value that still has to be read. So the pass can run on nums itself, with no second array: writer only ever moves forward, one slot at a time, never back. That is exactly Read-Write Pointers: reader visits every value, and writer marks the boundary between the kept values and the rest.

Step 1: A fresh list

Build the kept values into a new list. Correct: each new value is compared with the new list's last entry. States the contract in one listing: keep a value only when it differs from the one before it, then copy the result back.

Step 1: A fresh listPython
1def remove_duplicates_brute(nums):
2 if not nums:
3 return 0
4 kept = [nums[0]] # A fresh list, not written in place
5 for i in range(1, len(nums)):
6 if nums[i] != kept[-1]:
7 kept.append(nums[i])
8 for i, value in enumerate(kept):
9 nums[i] = value # Copy the fresh list back over nums
10 return len(kept)

Cost: O(N) time, O(N) extra space for the fresh list.

Step 2: Compact in place

The fresh list's last entry is always nums[writer - 1], since writer counts the values kept so far. So there is no need for a second list: write each kept value straight into nums[writer], because writer never gets ahead of reader. Comparing against nums[writer] instead of nums[writer - 1] reads a slot that still holds an old, unread value.

Step 2: Compact in placePython
1+New line: def removeDuplicates(nums: List[int]) -> int:
2 if not nums:
3 return 0
4
5+New line: writer = 1 # Seed: nums[0] is unconditionally unique
6+New line: # 1. Read Every Item, Write Only The Kept Ones:
7+New line: # nums is sorted, so a value is new exactly when it differs from nums[writer - 1], the last value kept; writer never gets ahead of reader, so the write never overwrites an unread value.
8+New line: for reader in range(1, len(nums)):
9+New line: if nums[reader] != nums[writer - 1]:
10+New line: nums[writer] = nums[reader]
11+New line: writer += 1
12
13+New line: return writer # nums[0..writer-1] holds compacted unique elements

Cost: O(N) time, O(1) extra space.

Step 3: The named template

The loop does not change between problems. Only keep changes.

Step 3: The named templatePython
1+New line: from typing import Callable, List, TypeVar
2
3+New line: Item = TypeVar("Item")
4
5+New line: def read_and_write(items: List[Item], keep: Callable[[Item, int], bool]) -> int:
6+New line: """Read-Write Pointers: move every kept item to the front of items, in order, in place.
7+New line: keep(value, writer) returns True to keep value. writer is the number of items kept so far.
8+New line: items[:writer] holds those items, so keep may compare value with items[writer - 1].
9+New line: Precondition: keep reads only value and items[:writer], never a slot at writer or later.
10+New line: Returns writer. The slots from writer on still hold old values. Move Zeroes writes its zeros there afterwards.
11+New line: N = len(items). T = the cost of one keep call.
12+New line: Time: O(N * T) | Space: O(1)
13+New line: """
14+New line: writer = 0 # items[:writer] holds the kept items, in order
15 # 1. Read Every Item, Write Only The Kept Ones:
16+New line: # writer never gets ahead of reader, so items[writer] = items[reader] always lands on a slot whose old value has already been read, never one still waiting to be read.
17+New line: for reader in range(len(items)):
18+New line: if keep(items[reader], writer):
19+New line: items[writer] = items[reader]
20 writer += 1
21
22 return writer # The number of kept items

Cost: O(N * T), where T is the cost of one keep call.

BeforeAfterWhat changed
if nums[reader] != nums[writer - 1]:if keep(items[reader], writer):The comparison moves inside keep, which also receives writer so it can look at items[writer - 1].
nums[writer] = nums[reader]items[writer] = items[reader]Same write, generic names.
writer += 1writer += 1Identical.
return writerreturn writerIdentical.

Practice

Canonical LeetCode:Remove Duplicates from Sorted Array (LC 26)Remove Duplicates from Sorted Array II (LC 80)Move Zeroes (LC 283)Sort Colors (LC 75)Partition Array According to Given Pivot (LC 2161)Remove Element (LC 27)Sort Array By Parity (LC 905)Rearrange Array Elements by Sign (LC 2149)
Theory Context•Two Pointers & Sliding Window
P1EasyMental model~4 min speedrun

Read/Write Pointers (In-Place Array Modification, Dutch National Flag)

Grows into
Cyclic Sort
Unlocks
Quickselect
The problem

Remove Duplicates from Sorted Array (LC 26)(opens leetcode.com)

You're given an already-sorted (smallest to largest) integer array nums. Rewrite it in-place so every value appears just once, keeping the surviving values in their original relative order.

Say k is the count of distinct values in nums. Once the duplicates are gone, hand back that count k, and make sure the first k slots of nums hold those distinct values in sorted order — whatever sits past index k - 1 is never inspected.

Custom Judge:

We compare the k you return with the length of the expected trimmed array, then check that each of the first k slots of nums holds the expected value for that position. Anything after slot k - 1 is ignored.

Example 1
Input:nums = [1,1,2]
Output:2, nums = [1,2,_]
k comes back as 2, with slots 0 and 1 of nums holding 1 and 2 in order. Whatever is left past index k-1 (shown as an underscore) is never checked.

Read-Write Pointers

reader -> writer -> (Compaction Frontier)
Spot it when: You must remove, deduplicate or move elements in place and return the new length (or leave the array partitioned).
Read-Write Pointers
Python
1from typing import Callable, List, TypeVar
2 
3Item = TypeVar("Item")
4 
5 
6def read_and_write(items: List[Item], keep: Callable[[Item, int], bool]) -> int:
7 """Read-Write Pointers: move every kept item to the front of items, in order, in place.
8 keep(value, writer) returns True to keep value. writer is the number of items kept so far.
9 items[:writer] holds those items, so keep may compare value with items[writer - 1].
10 Precondition: keep reads only value and items[:writer], never a slot at writer or later.
11 Returns writer. The slots from writer on still hold old values. Move Zeroes writes its zeros there afterwards.
12 N = len(items). T = the cost of one keep call.
13 Time: O(N * T) | Space: O(1)
14 """
15 writer = 0 # items[:writer] holds the kept items, in order
16 # 1. Read Every Item, Write Only The Kept Ones:
17 # writer never gets ahead of reader, so items[writer] = items[reader] always lands on a slot whose old value has already been read, never one still waiting to be read.
18 for reader in range(len(items)):
19 if keep(items[reader], writer):
20 items[writer] = items[reader]
21 writer += 1
22 
23 return writer # The number of kept items
Invariant: nums[0:writer] holds, in order, exactly the kept elements among nums[0:reader].

writer marks where the next kept element goes; everything before it is final. reader visits every element and copies it to writer only when it should be kept.

Since writer never passes reader, a write never lands on an element that still has to be read, so the array can be rewritten in place. For sorted deduplication, start both at 1, because nums[0] is always kept.

The trap is comparing with the wrong slot: check nums[reader] against nums[writer - 1], the last element kept, not nums[writer], the next free slot, which still holds an old value; on LeetCode Example 2 of Remove Duplicates from Sorted Array (LC 26), [0,0,1,1,1,2,2,3,3,4], the wrong slot keeps 7 values instead of 5.

See it run

Read-Write Pointers, step by step

reader 1 · writer 1

reader 1 · writer 1

reader 2 · writer 1

reader 3 · writer 2

reader 4 · writer 2

reader 5 · writer 2

reader 6 · writer 3

reader 7 · writer 3

reader 8 · writer 4

reader 9 · writer 4

reader 10 · writer 5 · answer 5

Each box is one entry of nums, with its index above it. Copies are made inside the same array.

  • kept a final value before writer
  • writer the next free slot
  • reader the entry being read
  • reader, writer reader and writer on the same entry

Start: nums[0] is always kept, so writer = 1 and reader = 1. Everything before writer is final.

nums[1] = 0 equals nums[writer - 1] = 0, so skip it. writer stays at 1.

nums[2] = 1 differs from nums[writer - 1] = nums[0] = 0, so copy it to nums[1]. Then writer becomes 2.

nums[3] = 1 equals nums[writer - 1] = 1, so skip it. writer stays at 2.

nums[4] = 1 equals nums[writer - 1] = 1, so skip it. writer stays at 2.

nums[5] = 2 differs from nums[writer - 1] = nums[1] = 1, so copy it to nums[2]. Then writer becomes 3.

nums[6] = 2 equals nums[writer - 1] = 2, so skip it. The free slot nums[3] still holds the old value 1, so comparing with it would keep a repeat.

nums[7] = 3 differs from nums[writer - 1] = nums[2] = 2, so copy it to nums[3]. Then writer becomes 4.

nums[8] = 3 equals nums[writer - 1] = 3, so skip it. writer stays at 4.

nums[9] = 4 differs from nums[writer - 1] = nums[3] = 3, so copy it to nums[4]. Then writer becomes 5.

Done: reader passed the end, so return writer = 5. nums[0..4] holds the kept values in order.

00kept
10reader, writer
21
31
41
52
62
73
83
94
1 / 11
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Comparing with items[writer] instead of items[writer - 1]

    Mistake
    if nums[reader] != nums[writer]:
    Fix
    if nums[reader] != nums[writer - 1]:

    What happens: nums[writer - 1] is the last value kept. nums[writer] is the next free slot. It still holds an old value from the input. Comparing with that old value can keep a duplicate. It can also drop a value that is new.

    Example: Remove Duplicates from Sorted Array (LC 26), Example 2: nums = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4]
    Mistake: returns 7Fix: returns 5
  2. 2

    Gotcha 2: Comparing with items[reader - 2] instead of items[writer - 2]

    Mistake
    if nums[reader] != nums[reader - 2]:
    Fix
    if nums[reader] != nums[writer - 2]:

    What happens: Remove Duplicates from Sorted Array II (LC 80) keeps at most two copies of each value. A value is kept when it differs from nums[writer - 2], the second-to-last value kept. nums[reader - 2] may already hold a value that the writer copied there. Then the check compares with the wrong value and drops a copy that should stay.

  3. 3

    Gotcha 3: Leaving old values after writer in Move Zeroes (LC 283)

    Mistake
    nums[writer] = nums[reader]
    Fix
    nums[writer], nums[reader] = nums[reader], nums[writer]

    What happens: Move Zeroes (LC 283) must end with every zero after the non-zero values. Copying nums[reader] to nums[writer] leaves the old values after writer in place. Swap the two values instead. You can also write zeros from writer to the end after the loop. Either way, the non-zero values keep their order.

  4. 4

    Gotcha 4: Moving mid after a swap with high

    Mistake
    nums[mid], nums[high] = nums[high], nums[mid]
    high -= 1
    mid += 1
    Fix
    nums[mid], nums[high] = nums[high], nums[mid]
    high -= 1

    What happens: Sort Colors (LC 75) uses three pointers: low, mid and high. When nums[mid] is 2, it is swapped with nums[high], and high moves left. The value that arrives at mid has not been checked yet. Moving mid past it can leave a 0 or a 2 in the middle.

Not this one if…
  • You compare or pair elements from the two opposite ends, as in Valid Palindrome (LC 125), instead of keeping or dropping each element in order. See Converging Pointers
  • You need one value at a given rank, as in Kth Largest Element in an Array (LC 215), not the array compacted or split by a fixed rule. See Quickselect

Theory

The question we build

What we know

nums is sorted in ascending order. Equal values are always adjacent, so a value only needs to be compared with the last kept value to know whether it is a duplicate.

What the problem gives us

The input is nums, modified in place. Return the number of unique values, with that many unique values occupying the front of nums, in order.

The question we build

For each position reader, we ask: "does nums[reader] equal the last value already kept?" If it is new, where should it go? It goes right after the last kept value, at the first free slot.

Narrowing down to the template

Is nums[reader] kept, and where does it go? The first free slot is writer, and writer never gets ahead of reader, so a write never overwrites a value that still has to be read. So the pass can run on nums itself, with no second array: writer only ever moves forward, one slot at a time, never back. That is exactly Read-Write Pointers: reader visits every value, and writer marks the boundary between the kept values and the rest.

Step 1: A fresh list

Build the kept values into a new list. Correct: each new value is compared with the new list's last entry. States the contract in one listing: keep a value only when it differs from the one before it, then copy the result back.

Step 1: A fresh listPython
1def remove_duplicates_brute(nums):
2 if not nums:
3 return 0
4 kept = [nums[0]] # A fresh list, not written in place
5 for i in range(1, len(nums)):
6 if nums[i] != kept[-1]:
7 kept.append(nums[i])
8 for i, value in enumerate(kept):
9 nums[i] = value # Copy the fresh list back over nums
10 return len(kept)

Cost: O(N) time, O(N) extra space for the fresh list.

Step 2: Compact in place

The fresh list's last entry is always nums[writer - 1], since writer counts the values kept so far. So there is no need for a second list: write each kept value straight into nums[writer], because writer never gets ahead of reader. Comparing against nums[writer] instead of nums[writer - 1] reads a slot that still holds an old, unread value.

Step 2: Compact in placePython
1+New line: def removeDuplicates(nums: List[int]) -> int:
2 if not nums:
3 return 0
4
5+New line: writer = 1 # Seed: nums[0] is unconditionally unique
6+New line: # 1. Read Every Item, Write Only The Kept Ones:
7+New line: # nums is sorted, so a value is new exactly when it differs from nums[writer - 1], the last value kept; writer never gets ahead of reader, so the write never overwrites an unread value.
8+New line: for reader in range(1, len(nums)):
9+New line: if nums[reader] != nums[writer - 1]:
10+New line: nums[writer] = nums[reader]
11+New line: writer += 1
12
13+New line: return writer # nums[0..writer-1] holds compacted unique elements

Cost: O(N) time, O(1) extra space.

Step 3: The named template

The loop does not change between problems. Only keep changes.

Step 3: The named templatePython
1+New line: from typing import Callable, List, TypeVar
2
3+New line: Item = TypeVar("Item")
4
5+New line: def read_and_write(items: List[Item], keep: Callable[[Item, int], bool]) -> int:
6+New line: """Read-Write Pointers: move every kept item to the front of items, in order, in place.
7+New line: keep(value, writer) returns True to keep value. writer is the number of items kept so far.
8+New line: items[:writer] holds those items, so keep may compare value with items[writer - 1].
9+New line: Precondition: keep reads only value and items[:writer], never a slot at writer or later.
10+New line: Returns writer. The slots from writer on still hold old values. Move Zeroes writes its zeros there afterwards.
11+New line: N = len(items). T = the cost of one keep call.
12+New line: Time: O(N * T) | Space: O(1)
13+New line: """
14+New line: writer = 0 # items[:writer] holds the kept items, in order
15 # 1. Read Every Item, Write Only The Kept Ones:
16+New line: # writer never gets ahead of reader, so items[writer] = items[reader] always lands on a slot whose old value has already been read, never one still waiting to be read.
17+New line: for reader in range(len(items)):
18+New line: if keep(items[reader], writer):
19+New line: items[writer] = items[reader]
20 writer += 1
21
22 return writer # The number of kept items

Cost: O(N * T), where T is the cost of one keep call.

BeforeAfterWhat changed
if nums[reader] != nums[writer - 1]:if keep(items[reader], writer):The comparison moves inside keep, which also receives writer so it can look at items[writer - 1].
nums[writer] = nums[reader]items[writer] = items[reader]Same write, generic names.
writer += 1writer += 1Identical.
return writerreturn writerIdentical.

Practice

Canonical LeetCode:Remove Duplicates from Sorted Array (LC 26)Remove Duplicates from Sorted Array II (LC 80)Move Zeroes (LC 283)Sort Colors (LC 75)Partition Array According to Given Pivot (LC 2161)Remove Element (LC 27)Sort Array By Parity (LC 905)Rearrange Array Elements by Sign (LC 2149)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•