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

Builds on Hash Map, which you haven’t finished yet.

P0MediumBuilt on Hash Map~2 min speedrun

Start-Element Pruning (Longest Consecutive Sequence)

Built on
Hash MapStore the values in a set and walk a run only from its first value, so every value is visited once.
The problem

Longest Consecutive Sequence (LC 128)(opens leetcode.com)

You get an integer array nums, in no particular order. A run is a chain of whole numbers that each exceed the one before by exactly 1 (x, x + 1, x + 2, ...). Find the longest run whose values all appear somewhere in nums, at any positions, and return how many values it holds.

Your algorithm has to finish in O(n) time.

Example 1
Input:nums = [100,4,200,1,3,2]
Output:4
1, 2, 3 and 4 all appear, so the run [1, 2, 3, 4] holds 4 values.
1000412002133425

Hash-Set Sequence Scanning

nums -> seen = set(nums) -> walk upward only from a run's start
Spot it when: Triggers: "unsorted array", "must run in O(n) time", "longest consecutive". Sorting is ruled out by the time budget, and the real question is "is the next value present?", which a hash set answers in one lookup -- but a plain lookup from every value is still O(N^2) on one long run, so the model also needs a cheap way to know which values are worth walking from.
Hash-Set Sequence Scanning
Python
1def longestConsecutive(nums: list[int], delta: int = 1) -> int:
2 """Finds the length of the longest arithmetic run with common difference `delta`.
3 Called with the default delta=1, this is exactly LC 128's algorithm; LeetCode's
4 harness calls it with only `nums`, so the extra parameter never breaks the signature.
5 Time: O(N) | Space: O(N)
6 """
7 if not nums:
8 return 0
9 
10 seen = set(nums) # O(N) deduplication & constant-time lookups
11 best = 0
12 
13 for left in seen:
14 # Step 1: Start-Element Pruning
15 # If 'left - delta' exists, 'left' sits inside a run and cannot be the head.
16 if left - delta in seen:
17 continue
18 
19 # Step 2: Count Progression Steps from Head
20 # 'length' counts the total number of valid terms in the progression.
21 length = 1
22 while left + length * delta in seen:
23 length += 1
24 
25 # Step 3: Record Maximum Count
26 best = max(best, length)
27 
28 return best
Invariant: A run is walked only from its start, the value whose left - delta is absent from seen, so every value is visited by the upward walk of at most one run.

A run of consecutive values has exactly one smallest member, and that member is the only one in the run whose predecessor is missing from seen, so if left - delta in seen: continue finds every run's start in one lookup and only a start is ever walked upward. Across the whole array, every value is checked once by the for loop and walked past by at most one run's while loop -- the one it belongs to. The trap: walking the upward probe from every value, not only from run starts, re-walks every run once per value inside it -- the pruning check is what keeps every value visited by at most one walk.

See it run

Start-Element Pruning, step by step

left - · length - · probe - · best 0 · probes 0

left 100 · length - · probe 99 · best 0 · probes 0

left 100 · length 1 · probe 101 · best 1 · probes 1

left 4 · length - · probe 3 · best 1 · probes 1

left 200 · length - · probe 199 · best 1 · probes 1

left 200 · length 1 · probe 201 · best 1 · probes 2

left 1 · length - · probe 0 · best 1 · probes 2

left 1 · length 2 · probe 2 · best 1 · probes 3

left 1 · length 3 · probe 3 · best 1 · probes 4

left 1 · length 4 · probe 4 · best 1 · probes 5

left 1 · length 4 · probe 5 · best 4 · probes 6

left 3 · length - · probe 2 · best 4 · probes 6

left 2 · length - · probe 1 · best 4 · probes 6

left - · length - · probe - · best 4 · probes 6 · answer 4

The top row is nums. The bottom row shows the run length under the start of each run. The loop takes the values in the order of nums.

  • left a value that starts a run, because left - delta is not in seen
  • skip a value inside a run, skipped because left - delta is in seen
  • next a value found by the upward walk
  • length the length of the run that starts at left

Start: seen holds the 6 distinct values of nums. The loop looks at each value as left.

100 - 1 = 99 is not in seen, so 100 is the start of a run.

101 is not in seen, so this run has length 1. best = 1.

4 - 1 = 3 is in seen, so 4 is inside a run and is skipped. Without this test, 4 would walk the same run again.

200 - 1 = 199 is not in seen, so 200 is the start of a run.

201 is not in seen, so this run has length 1. best = 1.

1 - 1 = 0 is not in seen, so 1 is the start of a run.

2 is in seen, so length becomes 2.

3 is in seen, so length becomes 3.

4 is in seen, so length becomes 4.

5 is not in seen, so this run has length 4. best = 4.

3 - 1 = 2 is in seen, so 3 is inside a run and is skipped. Without this test, 3 would walk the same run again.

2 - 1 = 1 is in seen, so 2 is inside a run and is skipped. Without this test, 2 would walk the same run again.

Done: the longest run has 4 values. The walks used 6 probes, against 12 if every value had walked.

nums
0100
14
2200
31
43
52
length
1 / 14
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Skipping the start-element pruning check (the card's trap)

    Mistake
    for left in seen:
        length = 1
        while left + length * delta in seen:
            length += 1
        best = max(best, length)
    Fix
    if left - delta in seen:
        continue

    What happens: Without the pruning check, the walk starts from every value, not only from a run's start. Every value inside an N-long run then re-walks the same run from its own position. One run of length N alone costs about N^2 lookups instead of N. The problem's O(n) requirement forbids this.

  2. 2

    Gotcha 2: Counting a repeated value's occurrences as extra run length

    Mistake
    length += counts[x]  # adds every repeat of x, not one step
    Fix
    length += 1  # one step per distinct value in the run

    What happens: Adding a value's occurrence count into length treats every repeat of a value as a new step of the run. A run must count each distinct value once, no matter how many times it repeats in nums. [1, 0, 1, 2] holds the run [0, 1, 2], three values, not four.

    Example: nums = [1, 0, 1, 2]
    Mistake: returns 4Fix: returns 3
  3. 3

    Gotcha 3: Overwriting best instead of keeping the running maximum

    Mistake
    best = length
    Fix
    best = max(best, length)

    What happens: best = length replaces the running maximum with whichever run is processed last by the for left in seen loop. A later, shorter run then erases the true answer found by an earlier, longer run. max(best, length) is what keeps the longest run found so far.

    Example: nums = [1, 2, 3, 10, 11]
    Mistake: returns 2Fix: returns 3
  4. 4

    Gotcha 4: Treating delta as an index offset instead of multiplying

    Mistake
    while left + length in seen:
    Fix
    while left + length * delta in seen:

    What happens: The next term of the progression is left + length * delta, not left + length. Dropping the * delta factor silently falls back to step-1 behavior, so any delta other than 1 probes the wrong values and undercounts the run.

    Example: nums = [2, 4, 6, 8], delta = 2
    Mistake: returns 1Fix: returns 4
Not this one if…
  • Both models build a hash structure over every item once, but Group Anagrams (LC 49) groups items that share a computed key; Hash-Set Sequence Scanning instead prunes which values are worth walking from, and only walks upward from a run's start. See Hash Map
  • Longest Arithmetic Subsequence of Given Difference (LC 1218) asks for the same one-step neighbour, v - difference, but it wants a subsequence, and a subsequence must keep the order of the positions in arr. Switch cards as soon as the problem says subsequence or in order: Hash-Set Sequence Scanning reads seen and ignores positions, so on [1, 3, 2] with difference 1 it would answer 3, where the correct answer is 2 (3 comes before 2 in arr, so [1, 2, 3] is not a subsequence). See LIS

Deep Dive

The set answers "is the next value there?", but not "where should I start?"

Count and Lookup already gives the tool: seen = set(nums) answers "is this value present?" in one step. A run is a chain of values with a fixed step delta between neighbours, and the step test is left + length * delta in seen.

That is not enough. If the walk starts from every value, a run of N values is walked from its first value (N steps), from its second (N - 1 steps), and so on. The total is about N^2 / 2 lookups. The problem asks for O(n).

The extra idea of this card is a cheap test for which values are worth walking from. Every run has exactly one smallest value. That value is the only one in its run whose predecessor left - delta is missing from seen.

So if left - delta in seen: continue finds every run's start with one lookup. The loop then walks upward from starts only.

Traced on [100, 4, 200, 1, 3, 2]

seen is {100, 4, 200, 1, 3, 2} and delta = 1. The board takes the values in the order of nums. The Python set may iterate in another order, and the answer is the same.

left = 100: 99 is not in seen, so 100 is a start. 101 is not in seen, so length = 1. left = 4: 3 is in seen, so 4 is skipped.

left = 200: 199 is not in seen, so 200 is a start, and 201 is absent, so length = 1. left = 1: 0 is not in seen, so 1 is a start. The walk finds 2, 3 and 4, then 5 is absent. So length = 4 and best = 4.

left = 3 and left = 2 are skipped, because 2 and 1 are in seen. The result is 4, for the run 1, 2, 3, 4.

The walks used 6 probes. If every value had walked upward, the same example would use 12. For one run of 1000 values, the walk from every value makes 499,500 successful probes. The pruned loop makes about 1,000 probes plus 1,000 start checks.

Why every value is walked at most once

A walk starts only at a run's smallest value. Each value belongs to exactly one run, so it is passed over by at most one walk: the walk of its own run.

The for loop checks each value once. The while loops together probe every value of every run once, plus one failed probe per run. Both counts are at most 2N, so the time is O(N). The set costs O(N) space.

Duplicates need no work. A set keeps one copy of each value, so [1, 0, 1, 2] has the run 0, 1, 2 of length 3. Counting the repeated 1 as a second step would return 4.

best = max(best, length) keeps the longest run so far. best = length would let a later short run erase the answer. For [1, 2, 3, 10, 11] it returns 2 instead of 3.

The delta parameter

The blueprint generalises the step to delta. The next term after length terms is left + length * delta. With delta = 1 this is Longest Consecutive Sequence (LC 128).

Dropping the factor, left + length, silently uses a step of 1. For [2, 4, 6, 8] and delta = 2, the right answer is 4 and the wrong test gives 1.

The start test uses the same step: left - delta. If the start test and the walk used different steps, a run could be skipped or walked twice.

60-Second Pitch

"Sorting costs O(N log N), so I use a set. The question of whether the next value is there is one lookup. A walk from every value would still cost O(N^2) on one long run. So I walk only from the start of a run. A value is a start when left - delta is not in the set. For a start I count upward while left + length * delta is in the set, and I keep the maximum length. Each value is passed over by at most one walk, so the time is O(N) and the space is O(N) for the set."

Common Questions

Why is a loop inside a loop still O(N)?

The inner while runs only from run starts. Each value belongs to one run, so the inner loops together probe each value once, plus one failed probe per run. The inner work is shared across the outer loop, not repeated.

Why use a set instead of sorting?

Sorting costs O(N log N), and the problem requires O(n). A set costs O(N) to build and answers each "is the next value present?" in constant time on average.

What happens with duplicate values?

The set keeps one copy of each value, so duplicates do not change any run. [1, 0, 1, 2] has the run 0, 1, 2 and the answer 3.

What does the card add to Count and Lookup?

Count and Lookup gives the set and the constant-time lookup. This card adds the test left - delta in seen, which decides where a walk may start. Without the test, the lookups alone still cost O(N^2) on one long run.

Could Union-Find solve the same problem?

Yes. Join each value with its neighbour value + 1 when that neighbour is present, and take the largest set size. It also runs in near-linear time. The set with start pruning needs less code and no extra structure.

SWE Reasoning

Spot it

  • "Unsorted array", "longest consecutive" and "must run in O(n) time" appear together (Longest Consecutive Sequence (LC 128)).
  • Sorting would take O(N log N), so the question has to be answered by lookups.
  • A run has one start, and the start can be recognised by one lookup of its predecessor.
  • A plain lookup walk from every value is still O(N^2) on one long run.

The invariant, step by step

  1. seen = set(nums) removes duplicates and gives constant-time lookups.
  2. For each left in seen, test left - delta in seen. If it is there, left is inside a run, and the loop continues.
  3. Otherwise left is the smallest value of its run. Set length = 1.
  4. While left + length * delta in seen, add 1 to length. The walk visits only values of this run.
  5. best = max(best, length) keeps the longest run.
  6. Every value is walked by at most one run, so the total work is O(N).

Traps

  • No pruning test. Every value walks upward. One run of N values costs about N^2 / 2 lookups, which breaks the O(n) requirement.
  • Counting repeats. length += counts[x] makes [1, 0, 1, 2] return 4 instead of 3.
  • best = length. A later, shorter run erases the answer. [1, 2, 3, 10, 11] returns 2 instead of 3.
  • left + length without delta. Any delta other than 1 probes the wrong values.

Practice

Canonical LeetCode:Longest Consecutive Sequence (LC 128)Longest Square Streak in an Array (LC 2501)
Theory Context•Two Pointers & Sliding Window

Builds on Hash Map, which you haven’t finished yet.

P0MediumBuilt on Hash Map~2 min speedrun

Start-Element Pruning (Longest Consecutive Sequence)

Built on
Hash MapStore the values in a set and walk a run only from its first value, so every value is visited once.
The problem

Longest Consecutive Sequence (LC 128)(opens leetcode.com)

You get an integer array nums, in no particular order. A run is a chain of whole numbers that each exceed the one before by exactly 1 (x, x + 1, x + 2, ...). Find the longest run whose values all appear somewhere in nums, at any positions, and return how many values it holds.

Your algorithm has to finish in O(n) time.

Example 1
Input:nums = [100,4,200,1,3,2]
Output:4
1, 2, 3 and 4 all appear, so the run [1, 2, 3, 4] holds 4 values.
1000412002133425

Hash-Set Sequence Scanning

nums -> seen = set(nums) -> walk upward only from a run's start
Spot it when: Triggers: "unsorted array", "must run in O(n) time", "longest consecutive". Sorting is ruled out by the time budget, and the real question is "is the next value present?", which a hash set answers in one lookup -- but a plain lookup from every value is still O(N^2) on one long run, so the model also needs a cheap way to know which values are worth walking from.
Hash-Set Sequence Scanning
Python
1def longestConsecutive(nums: list[int], delta: int = 1) -> int:
2 """Finds the length of the longest arithmetic run with common difference `delta`.
3 Called with the default delta=1, this is exactly LC 128's algorithm; LeetCode's
4 harness calls it with only `nums`, so the extra parameter never breaks the signature.
5 Time: O(N) | Space: O(N)
6 """
7 if not nums:
8 return 0
9 
10 seen = set(nums) # O(N) deduplication & constant-time lookups
11 best = 0
12 
13 for left in seen:
14 # Step 1: Start-Element Pruning
15 # If 'left - delta' exists, 'left' sits inside a run and cannot be the head.
16 if left - delta in seen:
17 continue
18 
19 # Step 2: Count Progression Steps from Head
20 # 'length' counts the total number of valid terms in the progression.
21 length = 1
22 while left + length * delta in seen:
23 length += 1
24 
25 # Step 3: Record Maximum Count
26 best = max(best, length)
27 
28 return best
Invariant: A run is walked only from its start, the value whose left - delta is absent from seen, so every value is visited by the upward walk of at most one run.

A run of consecutive values has exactly one smallest member, and that member is the only one in the run whose predecessor is missing from seen, so if left - delta in seen: continue finds every run's start in one lookup and only a start is ever walked upward. Across the whole array, every value is checked once by the for loop and walked past by at most one run's while loop -- the one it belongs to. The trap: walking the upward probe from every value, not only from run starts, re-walks every run once per value inside it -- the pruning check is what keeps every value visited by at most one walk.

See it run

Start-Element Pruning, step by step

left - · length - · probe - · best 0 · probes 0

left 100 · length - · probe 99 · best 0 · probes 0

left 100 · length 1 · probe 101 · best 1 · probes 1

left 4 · length - · probe 3 · best 1 · probes 1

left 200 · length - · probe 199 · best 1 · probes 1

left 200 · length 1 · probe 201 · best 1 · probes 2

left 1 · length - · probe 0 · best 1 · probes 2

left 1 · length 2 · probe 2 · best 1 · probes 3

left 1 · length 3 · probe 3 · best 1 · probes 4

left 1 · length 4 · probe 4 · best 1 · probes 5

left 1 · length 4 · probe 5 · best 4 · probes 6

left 3 · length - · probe 2 · best 4 · probes 6

left 2 · length - · probe 1 · best 4 · probes 6

left - · length - · probe - · best 4 · probes 6 · answer 4

The top row is nums. The bottom row shows the run length under the start of each run. The loop takes the values in the order of nums.

  • left a value that starts a run, because left - delta is not in seen
  • skip a value inside a run, skipped because left - delta is in seen
  • next a value found by the upward walk
  • length the length of the run that starts at left

Start: seen holds the 6 distinct values of nums. The loop looks at each value as left.

100 - 1 = 99 is not in seen, so 100 is the start of a run.

101 is not in seen, so this run has length 1. best = 1.

4 - 1 = 3 is in seen, so 4 is inside a run and is skipped. Without this test, 4 would walk the same run again.

200 - 1 = 199 is not in seen, so 200 is the start of a run.

201 is not in seen, so this run has length 1. best = 1.

1 - 1 = 0 is not in seen, so 1 is the start of a run.

2 is in seen, so length becomes 2.

3 is in seen, so length becomes 3.

4 is in seen, so length becomes 4.

5 is not in seen, so this run has length 4. best = 4.

3 - 1 = 2 is in seen, so 3 is inside a run and is skipped. Without this test, 3 would walk the same run again.

2 - 1 = 1 is in seen, so 2 is inside a run and is skipped. Without this test, 2 would walk the same run again.

Done: the longest run has 4 values. The walks used 6 probes, against 12 if every value had walked.

nums
0100
14
2200
31
43
52
length
1 / 14
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Skipping the start-element pruning check (the card's trap)

    Mistake
    for left in seen:
        length = 1
        while left + length * delta in seen:
            length += 1
        best = max(best, length)
    Fix
    if left - delta in seen:
        continue

    What happens: Without the pruning check, the walk starts from every value, not only from a run's start. Every value inside an N-long run then re-walks the same run from its own position. One run of length N alone costs about N^2 lookups instead of N. The problem's O(n) requirement forbids this.

  2. 2

    Gotcha 2: Counting a repeated value's occurrences as extra run length

    Mistake
    length += counts[x]  # adds every repeat of x, not one step
    Fix
    length += 1  # one step per distinct value in the run

    What happens: Adding a value's occurrence count into length treats every repeat of a value as a new step of the run. A run must count each distinct value once, no matter how many times it repeats in nums. [1, 0, 1, 2] holds the run [0, 1, 2], three values, not four.

    Example: nums = [1, 0, 1, 2]
    Mistake: returns 4Fix: returns 3
  3. 3

    Gotcha 3: Overwriting best instead of keeping the running maximum

    Mistake
    best = length
    Fix
    best = max(best, length)

    What happens: best = length replaces the running maximum with whichever run is processed last by the for left in seen loop. A later, shorter run then erases the true answer found by an earlier, longer run. max(best, length) is what keeps the longest run found so far.

    Example: nums = [1, 2, 3, 10, 11]
    Mistake: returns 2Fix: returns 3
  4. 4

    Gotcha 4: Treating delta as an index offset instead of multiplying

    Mistake
    while left + length in seen:
    Fix
    while left + length * delta in seen:

    What happens: The next term of the progression is left + length * delta, not left + length. Dropping the * delta factor silently falls back to step-1 behavior, so any delta other than 1 probes the wrong values and undercounts the run.

    Example: nums = [2, 4, 6, 8], delta = 2
    Mistake: returns 1Fix: returns 4
Not this one if…
  • Both models build a hash structure over every item once, but Group Anagrams (LC 49) groups items that share a computed key; Hash-Set Sequence Scanning instead prunes which values are worth walking from, and only walks upward from a run's start. See Hash Map
  • Longest Arithmetic Subsequence of Given Difference (LC 1218) asks for the same one-step neighbour, v - difference, but it wants a subsequence, and a subsequence must keep the order of the positions in arr. Switch cards as soon as the problem says subsequence or in order: Hash-Set Sequence Scanning reads seen and ignores positions, so on [1, 3, 2] with difference 1 it would answer 3, where the correct answer is 2 (3 comes before 2 in arr, so [1, 2, 3] is not a subsequence). See LIS

Deep Dive

The set answers "is the next value there?", but not "where should I start?"

Count and Lookup already gives the tool: seen = set(nums) answers "is this value present?" in one step. A run is a chain of values with a fixed step delta between neighbours, and the step test is left + length * delta in seen.

That is not enough. If the walk starts from every value, a run of N values is walked from its first value (N steps), from its second (N - 1 steps), and so on. The total is about N^2 / 2 lookups. The problem asks for O(n).

The extra idea of this card is a cheap test for which values are worth walking from. Every run has exactly one smallest value. That value is the only one in its run whose predecessor left - delta is missing from seen.

So if left - delta in seen: continue finds every run's start with one lookup. The loop then walks upward from starts only.

Traced on [100, 4, 200, 1, 3, 2]

seen is {100, 4, 200, 1, 3, 2} and delta = 1. The board takes the values in the order of nums. The Python set may iterate in another order, and the answer is the same.

left = 100: 99 is not in seen, so 100 is a start. 101 is not in seen, so length = 1. left = 4: 3 is in seen, so 4 is skipped.

left = 200: 199 is not in seen, so 200 is a start, and 201 is absent, so length = 1. left = 1: 0 is not in seen, so 1 is a start. The walk finds 2, 3 and 4, then 5 is absent. So length = 4 and best = 4.

left = 3 and left = 2 are skipped, because 2 and 1 are in seen. The result is 4, for the run 1, 2, 3, 4.

The walks used 6 probes. If every value had walked upward, the same example would use 12. For one run of 1000 values, the walk from every value makes 499,500 successful probes. The pruned loop makes about 1,000 probes plus 1,000 start checks.

Why every value is walked at most once

A walk starts only at a run's smallest value. Each value belongs to exactly one run, so it is passed over by at most one walk: the walk of its own run.

The for loop checks each value once. The while loops together probe every value of every run once, plus one failed probe per run. Both counts are at most 2N, so the time is O(N). The set costs O(N) space.

Duplicates need no work. A set keeps one copy of each value, so [1, 0, 1, 2] has the run 0, 1, 2 of length 3. Counting the repeated 1 as a second step would return 4.

best = max(best, length) keeps the longest run so far. best = length would let a later short run erase the answer. For [1, 2, 3, 10, 11] it returns 2 instead of 3.

The delta parameter

The blueprint generalises the step to delta. The next term after length terms is left + length * delta. With delta = 1 this is Longest Consecutive Sequence (LC 128).

Dropping the factor, left + length, silently uses a step of 1. For [2, 4, 6, 8] and delta = 2, the right answer is 4 and the wrong test gives 1.

The start test uses the same step: left - delta. If the start test and the walk used different steps, a run could be skipped or walked twice.

60-Second Pitch

"Sorting costs O(N log N), so I use a set. The question of whether the next value is there is one lookup. A walk from every value would still cost O(N^2) on one long run. So I walk only from the start of a run. A value is a start when left - delta is not in the set. For a start I count upward while left + length * delta is in the set, and I keep the maximum length. Each value is passed over by at most one walk, so the time is O(N) and the space is O(N) for the set."

Common Questions

Why is a loop inside a loop still O(N)?

The inner while runs only from run starts. Each value belongs to one run, so the inner loops together probe each value once, plus one failed probe per run. The inner work is shared across the outer loop, not repeated.

Why use a set instead of sorting?

Sorting costs O(N log N), and the problem requires O(n). A set costs O(N) to build and answers each "is the next value present?" in constant time on average.

What happens with duplicate values?

The set keeps one copy of each value, so duplicates do not change any run. [1, 0, 1, 2] has the run 0, 1, 2 and the answer 3.

What does the card add to Count and Lookup?

Count and Lookup gives the set and the constant-time lookup. This card adds the test left - delta in seen, which decides where a walk may start. Without the test, the lookups alone still cost O(N^2) on one long run.

Could Union-Find solve the same problem?

Yes. Join each value with its neighbour value + 1 when that neighbour is present, and take the largest set size. It also runs in near-linear time. The set with start pruning needs less code and no extra structure.

SWE Reasoning

Spot it

  • "Unsorted array", "longest consecutive" and "must run in O(n) time" appear together (Longest Consecutive Sequence (LC 128)).
  • Sorting would take O(N log N), so the question has to be answered by lookups.
  • A run has one start, and the start can be recognised by one lookup of its predecessor.
  • A plain lookup walk from every value is still O(N^2) on one long run.

The invariant, step by step

  1. seen = set(nums) removes duplicates and gives constant-time lookups.
  2. For each left in seen, test left - delta in seen. If it is there, left is inside a run, and the loop continues.
  3. Otherwise left is the smallest value of its run. Set length = 1.
  4. While left + length * delta in seen, add 1 to length. The walk visits only values of this run.
  5. best = max(best, length) keeps the longest run.
  6. Every value is walked by at most one run, so the total work is O(N).

Traps

  • No pruning test. Every value walks upward. One run of N values costs about N^2 / 2 lookups, which breaks the O(n) requirement.
  • Counting repeats. length += counts[x] makes [1, 0, 1, 2] return 4 instead of 3.
  • best = length. A later, shorter run erases the answer. [1, 2, 3, 10, 11] returns 2 instead of 3.
  • left + length without delta. Any delta other than 1 probes the wrong values.

Practice

Canonical LeetCode:Longest Consecutive Sequence (LC 128)Longest Square Streak in an Array (LC 2501)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•