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 Fixed Window, which you haven’t finished yet.

P1MediumMental model~4 min speedrun

Variable-Size Sliding Window (Longest Window)

Grows into
Shortest WindowAt-Most Trick
Unlocks
Dual Monotonic Deque
The problem

Longest Substring Without Repeating Characters (LC 3)(opens leetcode.com)

You have a string s. Among all of its contiguous substrings, find the longest one that never repeats a character, and report its length.

Example 1
Input:s = "abcabcbb"
Output:3
"abc" is a repeat-free run of length 3; "bca" and "cab" would also be valid answers of the same length.

Longest Window

left -> right -> (Check Before Growing, Contract To Repair)
Spot it when: You want the longest contiguous subarray or substring that stays within a limit: no repeats, at most k distinct values, at most k zeros flipped.
Longest Window
Python
1from typing import Callable
2 
3 
4def longest_window(n: int, add: Callable[[int], None], remove: Callable[[int], None], would_be_valid: Callable[[int], bool]) -> int:
5 """Longest Window: the length of the longest valid window [left, right] of indices 0..n-1.
6 Precondition: every shorter window inside a valid window is valid too, and the empty window is valid.
7 add(i) and remove(i) update the window's state for index i.
8 would_be_valid(i) reads whether adding index i to the current window would keep it valid, without changing the window's state.
9 T = the cost of one add, remove or would_be_valid call.
10 Time: O(N * T) | Space: O(1) besides the window's state
11 """
12 left = 0
13 max_len = 0
14 
15 for right in range(n):
16 # 1. Repair By Evicting From left:
17 # would_be_valid(right) asks whether adding index right would still be a valid window, without changing the window's state. Every window [j, right] with j < left contains everything [left, right] contains, so if adding right to [left, right] would still be invalid, no later left can keep right out of it; evicting left is the only way back to a window that right can be added to.
18 while not would_be_valid(right):
19 remove(left)
20 left += 1
21 # 2. Absorb Index right:
22 # Once would_be_valid(right) confirms the window stays valid, right is actually added.
23 add(right)
24 # 3. Record Only After The Add:
25 # The window [left, right] is now valid and fully updated, so its length can be measured.
26 max_len = max(max_len, right - left + 1)
27 
28 return max_len
Invariant: When max_len is updated, seen_chars is exactly the set of characters in s[left..right], with no repeats.

Before you add right to the window, check whether adding it would break the rule. If it would, move left forward until adding right is safe, then add right and record the window's length.

The window is always valid at the moment you add right, because right is only added once the check says adding it keeps the window valid, so the largest measurement is the answer. Moving left never loses a longer answer: any window that starts before left already contains everything [left, right] contains, so adding right to it would break the rule too.

The trap is answering the check by mutating the window's state to test it: the check must read what adding right would do without changing the window, or the add call right after it double-counts right.

See it run

Longest Window, step by step

left 0 · right - · seen_chars {} · max_len 0

left 0 · right 0 · seen_chars {a} · max_len 1

left 0 · right 1 · seen_chars {a, b} · max_len 2

left 0 · right 2 · seen_chars {a, b, c} · max_len 3

left 0 · right 3 · seen_chars {a, b, c} · max_len 3

left 1 · right 3 · seen_chars {b, c} · max_len 3

left 1 · right 3 · seen_chars {b, c, a} · max_len 3

left 1 · right 4 · seen_chars {b, c, a} · max_len 3

left 2 · right 4 · seen_chars {c, a} · max_len 3

left 2 · right 4 · seen_chars {c, a, b} · max_len 3

left 2 · right 5 · seen_chars {c, a, b} · max_len 3

left 3 · right 5 · seen_chars {a, b} · max_len 3

left 3 · right 5 · seen_chars {a, b, c} · max_len 3

left 3 · right 6 · seen_chars {a, b, c} · max_len 3

left 4 · right 6 · seen_chars {b, c} · max_len 3

left 5 · right 6 · seen_chars {c} · max_len 3

left 5 · right 6 · seen_chars {c, b} · max_len 3

left 5 · right 7 · seen_chars {c, b} · max_len 3

left 6 · right 7 · seen_chars {b} · max_len 3

left 7 · right 7 · seen_chars {} · max_len 3

left 7 · right 7 · seen_chars {b} · max_len 3

left 7 · right 7 · seen_chars {b} · max_len 3 · answer 3

Each box is one character of s, with its index above it. The window holds no repeated character at the moment max_len is measured.

  • left the first index of the window
  • right the index being added
  • left, right left and right on the same entry
  • window an entry inside the window
  • evicted the entry that just left the window

Start: left = 0, seen_chars is empty and max_len = 0. For each right, check first whether adding s[right] makes a repeat.

Add s[0] = a to seen_chars. The window has length 1, so max_len becomes 1.

Add s[1] = b to seen_chars. The window has length 2, so max_len becomes 2.

Add s[2] = c to seen_chars. The window has length 3, so max_len becomes 3.

s[3] = a is already in seen_chars, so adding it would make a repeat. Measuring now would give 4, which counts a window with a repeat.

Evict s[0] = a from seen_chars and move left to 1. s[3] = a is not in seen_chars, so it is safe to add.

Add s[3] = a to seen_chars. The window has length 3, so max_len stays 3.

s[4] = b is already in seen_chars, so adding it would make a repeat. Evict from left first.

Evict s[1] = b from seen_chars and move left to 2. s[4] = b is not in seen_chars, so it is safe to add.

Add s[4] = b to seen_chars. The window has length 3, so max_len stays 3.

s[5] = c is already in seen_chars, so adding it would make a repeat. Evict from left first.

Evict s[2] = c from seen_chars and move left to 3. s[5] = c is not in seen_chars, so it is safe to add.

Add s[5] = c to seen_chars. The window has length 3, so max_len stays 3.

s[6] = b is already in seen_chars, so adding it would make a repeat. Evict from left first.

Evict s[3] = a from seen_chars and move left to 4. s[6] = b is still in seen_chars, so evict again.

Evict s[4] = b from seen_chars and move left to 5. s[6] = b is not in seen_chars, so it is safe to add.

Add s[6] = b to seen_chars. The window has length 2, so max_len stays 3.

s[7] = b is already in seen_chars, so adding it would make a repeat. Evict from left first.

Evict s[5] = c from seen_chars and move left to 6. s[7] = b is still in seen_chars, so evict again.

Evict s[6] = b from seen_chars and move left to 7. s[7] = b is not in seen_chars, so it is safe to add.

Add s[7] = b to seen_chars. The window has length 1, so max_len stays 3.

Done: right reached the end, so return max_len = 3.

0a
1b
2c
3a
4b
5c
6b
7b
1 / 22
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Measuring the window before the while loop repairs it

    Mistake
    for right in range(len(s)):
        max_len = max(max_len, right - left + 1)
        while s[right] in seen_chars:
    Fix
    for right in range(len(s)):
        while s[right] in seen_chars:

    What happens: Before the while loop, s[right] may already be in the window. Then the window s[left..right] holds a letter twice. A length measured there counts an invalid window, so max_len can become too large. The while loop moves left until the repeat is gone. After that loop, right - left + 1 is the length of a valid window.

    Example: s = "abcabcbb"
    Mistake: returns 4Fix: returns 3
  2. 2

    Gotcha 2: Repairing the window with if instead of while

    Mistake
    if s[right] in seen_chars:
    Fix
    while s[right] in seen_chars:

    What happens: The earlier copy of s[right] can be several places after left. One step removes only s[left], which may be a different letter. Then the window still holds the repeat when it is measured.

    Example: s = "pwwkew"
    Mistake: returns 4Fix: returns 3
  3. 3

    Gotcha 3: Keeping map keys whose count dropped to 0

    Mistake
    count[fruits[left]] -= 1
    Fix
    count[fruits[left]] -= 1
    if count[fruits[left]] == 0:
        del count[fruits[left]]

    What happens: Fruit Into Baskets (LC 904) allows at most 2 fruit types in the window. It keeps a count for each type and checks len(count). When left moves, the count of fruits[left] goes down by 1. A type whose count reaches 0 is no longer in the window. If its key stays, len(count) still counts it, and the window shrinks more than needed.

  4. 4

    Gotcha 4: Using one window to count exactly k distinct values

    What happens: Subarrays with K Different Integers (LC 992) counts the subarrays with exactly k distinct values. For one right, a whole range of starts gives exactly k values. One window tracks only one start, so it misses the others. Count the subarrays with at most k distinct values, and subtract those with at most k - 1. That is the At-Most Trick.

Not this one if…
  • The question asks for the shortest window that meets a requirement, as in Minimum Window Substring (LC 76): adding elements helps the window instead of breaking it. See Shortest Window
  • The window length is fixed by the input, as in Find All Anagrams in a String (LC 438): every window has the size of p, so there is no longest one to find. See Fixed Window

Theory

The question we build

What we know

s is a sequence of characters. A window holds a repeat, or it does not; adding a character can only turn a valid window invalid, never the other way.

What the problem gives us

The input is s. Return the length of the longest substring with no repeated character.

The question we build

For each right, we ask: "does the window s[left..right] hold a repeat?" When it does, which starts can still give a valid window ending at right? A window [j, right] with j < left contains everything [left, right] contains, plus more, so if [left, right] already holds the repeat, [j, right] holds it too.

Narrowing down to the template

Which windows that end at right can still be the answer? If the window [left, right] holds a repeat, every window [j, right] with j < left holds it too, so left only moves right. So once left passes a start, no window from that start, ending anywhere at or after right, can ever become valid again: left never needs to move backward. That is exactly Longest Window: check whether adding right is still safe, repair by moving left forward until it is, then add right and measure.

Step 1: Every window

Check every window (left, right) with a fresh set. Correct: a repeat is caught by set membership. States the contract in one listing: the longest window with no repeated character.

Step 1: Every windowPython
1def length_of_longest_brute(s):
2 n = len(s)
3 best = 0
4 for left in range(n):
5 seen = set()
6 for right in range(left, n):
7 if s[right] in seen:
8 break # A fresh set per left: this window holds a repeat
9 seen.add(s[right])
10 best = max(best, right - left + 1)
11 return best

Cost: O(N^2) time, O(N) extra space.

Step 2: Check and repair

If adding right to the window [left, right - 1] would hold a repeat, adding right to any window [j, right - 1] with j < left would hold it too. So left never needs to move backward: before adding right, check whether it would create a repeat, and while it would, evict from left. Only once the while loop confirms adding right is safe is right actually added, so measuring after the add never counts a window that holds a repeat.

Step 2: Check and repairPython
1def lengthOfLongestSubstring(s: str) -> int:
2 seen_chars = set() # characters currently inside the window s[left..right - 1]
3 left = 0
4 max_len = 0
5 
6 # 1. Repair By Evicting From left:
7 # s[right] in seen_chars asks whether adding s[right] would create a repeat, without changing seen_chars. Every window [j, right] with j < left still holds the same duplicate, so left is the only pointer that can move.
8 for right in range(len(s)):
9 while s[right] in seen_chars:
10 seen_chars.remove(s[left])
11 left += 1
12 
13 # 2. Absorb Index right:
14 # Once the while loop confirms s[right] is not in seen_chars, it is actually added.
15 seen_chars.add(s[right])
16 # 3. Record Only After The Add:
17 max_len = max(max_len, right - left + 1)
18 
19 return max_len

Cost: O(N) time, O(min(N, Sigma)) space, where Sigma is the number of different characters.

Step 3: The named template

The loop does not change between problems. Only add, remove and would_be_valid change.

Step 3: The named templatePython
1+New line: from typing import Callable
2
3+New line: def longest_window(n: int, add: Callable[[int], None], remove: Callable[[int], None], would_be_valid: Callable[[int], bool]) -> int:
4+New line: """Longest Window: the length of the longest valid window [left, right] of indices 0..n-1.
5+New line: Precondition: every shorter window inside a valid window is valid too, and the empty window is valid.
6+New line: add(i) and remove(i) update the window's state for index i.
7+New line: would_be_valid(i) reads whether adding index i to the current window would keep it valid, without changing the window's state.
8+New line: T = the cost of one add, remove or would_be_valid call.
9+New line: Time: O(N * T) | Space: O(1) besides the window's state
10+New line: """
11 left = 0
12 max_len = 0
13
14+New line: for right in range(n):
15+New line: # 1. Repair By Evicting From left:
16+New line: # would_be_valid(right) asks whether adding index right would still be a valid window, without changing the window's state. Every window [j, right] with j < left contains everything [left, right] contains, so if adding right to [left, right] would still be invalid, no later left can keep right out of it; evicting left is the only way back to a window that right can be added to.
17+New line: while not would_be_valid(right):
18+New line: remove(left)
19 left += 1
20 # 2. Absorb Index right:
21+New line: # Once would_be_valid(right) confirms the window stays valid, right is actually added.
22+New line: add(right)
23 # 3. Record Only After The Add:
24+New line: # The window [left, right] is now valid and fully updated, so its length can be measured.
25 max_len = max(max_len, right - left + 1)
26
27 return max_len

Cost: O(N * T), where T is the cost of one add, remove or would_be_valid call.

BeforeAfterWhat changed
while s[right] in seen_chars: seen_chars.remove(s[left]); left += 1while not would_be_valid(right): remove(left); left += 1The repeat check becomes would_be_valid(right), which reads whether adding right would still be valid without changing the window, and the eviction becomes remove.
seen_chars.add(s[right])add(right)Absorbing the incoming character becomes add, still called only after would_be_valid(right) confirms the window stays valid.
max_len = max(max_len, right - left + 1)max_len = max(max_len, right - left + 1)Identical, still after the add.

Practice

Canonical LeetCode:Longest Substring Without Repeating Characters (LC 3)Fruit Into Baskets (LC 904)Longest Subarray of 1's After Deleting One Element (LC 1493)Longest Substring with At Most K Distinct Characters (LC 340)Max Consecutive Ones II (LC 487)Minimum Operations to Reduce X to Zero (LC 1658)Longest Substring with At Most Two Distinct Characters (LC 159)Longest Repeating Character Replacement (LC 424)Max Consecutive Ones III (LC 1004)
Theory Context•Two Pointers & Sliding Window

Builds on Fixed Window, which you haven’t finished yet.

P1MediumMental model~4 min speedrun

Variable-Size Sliding Window (Longest Window)

Grows into
Shortest WindowAt-Most Trick
Unlocks
Dual Monotonic Deque
The problem

Longest Substring Without Repeating Characters (LC 3)(opens leetcode.com)

You have a string s. Among all of its contiguous substrings, find the longest one that never repeats a character, and report its length.

Example 1
Input:s = "abcabcbb"
Output:3
"abc" is a repeat-free run of length 3; "bca" and "cab" would also be valid answers of the same length.

Longest Window

left -> right -> (Check Before Growing, Contract To Repair)
Spot it when: You want the longest contiguous subarray or substring that stays within a limit: no repeats, at most k distinct values, at most k zeros flipped.
Longest Window
Python
1from typing import Callable
2 
3 
4def longest_window(n: int, add: Callable[[int], None], remove: Callable[[int], None], would_be_valid: Callable[[int], bool]) -> int:
5 """Longest Window: the length of the longest valid window [left, right] of indices 0..n-1.
6 Precondition: every shorter window inside a valid window is valid too, and the empty window is valid.
7 add(i) and remove(i) update the window's state for index i.
8 would_be_valid(i) reads whether adding index i to the current window would keep it valid, without changing the window's state.
9 T = the cost of one add, remove or would_be_valid call.
10 Time: O(N * T) | Space: O(1) besides the window's state
11 """
12 left = 0
13 max_len = 0
14 
15 for right in range(n):
16 # 1. Repair By Evicting From left:
17 # would_be_valid(right) asks whether adding index right would still be a valid window, without changing the window's state. Every window [j, right] with j < left contains everything [left, right] contains, so if adding right to [left, right] would still be invalid, no later left can keep right out of it; evicting left is the only way back to a window that right can be added to.
18 while not would_be_valid(right):
19 remove(left)
20 left += 1
21 # 2. Absorb Index right:
22 # Once would_be_valid(right) confirms the window stays valid, right is actually added.
23 add(right)
24 # 3. Record Only After The Add:
25 # The window [left, right] is now valid and fully updated, so its length can be measured.
26 max_len = max(max_len, right - left + 1)
27 
28 return max_len
Invariant: When max_len is updated, seen_chars is exactly the set of characters in s[left..right], with no repeats.

Before you add right to the window, check whether adding it would break the rule. If it would, move left forward until adding right is safe, then add right and record the window's length.

The window is always valid at the moment you add right, because right is only added once the check says adding it keeps the window valid, so the largest measurement is the answer. Moving left never loses a longer answer: any window that starts before left already contains everything [left, right] contains, so adding right to it would break the rule too.

The trap is answering the check by mutating the window's state to test it: the check must read what adding right would do without changing the window, or the add call right after it double-counts right.

See it run

Longest Window, step by step

left 0 · right - · seen_chars {} · max_len 0

left 0 · right 0 · seen_chars {a} · max_len 1

left 0 · right 1 · seen_chars {a, b} · max_len 2

left 0 · right 2 · seen_chars {a, b, c} · max_len 3

left 0 · right 3 · seen_chars {a, b, c} · max_len 3

left 1 · right 3 · seen_chars {b, c} · max_len 3

left 1 · right 3 · seen_chars {b, c, a} · max_len 3

left 1 · right 4 · seen_chars {b, c, a} · max_len 3

left 2 · right 4 · seen_chars {c, a} · max_len 3

left 2 · right 4 · seen_chars {c, a, b} · max_len 3

left 2 · right 5 · seen_chars {c, a, b} · max_len 3

left 3 · right 5 · seen_chars {a, b} · max_len 3

left 3 · right 5 · seen_chars {a, b, c} · max_len 3

left 3 · right 6 · seen_chars {a, b, c} · max_len 3

left 4 · right 6 · seen_chars {b, c} · max_len 3

left 5 · right 6 · seen_chars {c} · max_len 3

left 5 · right 6 · seen_chars {c, b} · max_len 3

left 5 · right 7 · seen_chars {c, b} · max_len 3

left 6 · right 7 · seen_chars {b} · max_len 3

left 7 · right 7 · seen_chars {} · max_len 3

left 7 · right 7 · seen_chars {b} · max_len 3

left 7 · right 7 · seen_chars {b} · max_len 3 · answer 3

Each box is one character of s, with its index above it. The window holds no repeated character at the moment max_len is measured.

  • left the first index of the window
  • right the index being added
  • left, right left and right on the same entry
  • window an entry inside the window
  • evicted the entry that just left the window

Start: left = 0, seen_chars is empty and max_len = 0. For each right, check first whether adding s[right] makes a repeat.

Add s[0] = a to seen_chars. The window has length 1, so max_len becomes 1.

Add s[1] = b to seen_chars. The window has length 2, so max_len becomes 2.

Add s[2] = c to seen_chars. The window has length 3, so max_len becomes 3.

s[3] = a is already in seen_chars, so adding it would make a repeat. Measuring now would give 4, which counts a window with a repeat.

Evict s[0] = a from seen_chars and move left to 1. s[3] = a is not in seen_chars, so it is safe to add.

Add s[3] = a to seen_chars. The window has length 3, so max_len stays 3.

s[4] = b is already in seen_chars, so adding it would make a repeat. Evict from left first.

Evict s[1] = b from seen_chars and move left to 2. s[4] = b is not in seen_chars, so it is safe to add.

Add s[4] = b to seen_chars. The window has length 3, so max_len stays 3.

s[5] = c is already in seen_chars, so adding it would make a repeat. Evict from left first.

Evict s[2] = c from seen_chars and move left to 3. s[5] = c is not in seen_chars, so it is safe to add.

Add s[5] = c to seen_chars. The window has length 3, so max_len stays 3.

s[6] = b is already in seen_chars, so adding it would make a repeat. Evict from left first.

Evict s[3] = a from seen_chars and move left to 4. s[6] = b is still in seen_chars, so evict again.

Evict s[4] = b from seen_chars and move left to 5. s[6] = b is not in seen_chars, so it is safe to add.

Add s[6] = b to seen_chars. The window has length 2, so max_len stays 3.

s[7] = b is already in seen_chars, so adding it would make a repeat. Evict from left first.

Evict s[5] = c from seen_chars and move left to 6. s[7] = b is still in seen_chars, so evict again.

Evict s[6] = b from seen_chars and move left to 7. s[7] = b is not in seen_chars, so it is safe to add.

Add s[7] = b to seen_chars. The window has length 1, so max_len stays 3.

Done: right reached the end, so return max_len = 3.

0a
1b
2c
3a
4b
5c
6b
7b
1 / 22
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Measuring the window before the while loop repairs it

    Mistake
    for right in range(len(s)):
        max_len = max(max_len, right - left + 1)
        while s[right] in seen_chars:
    Fix
    for right in range(len(s)):
        while s[right] in seen_chars:

    What happens: Before the while loop, s[right] may already be in the window. Then the window s[left..right] holds a letter twice. A length measured there counts an invalid window, so max_len can become too large. The while loop moves left until the repeat is gone. After that loop, right - left + 1 is the length of a valid window.

    Example: s = "abcabcbb"
    Mistake: returns 4Fix: returns 3
  2. 2

    Gotcha 2: Repairing the window with if instead of while

    Mistake
    if s[right] in seen_chars:
    Fix
    while s[right] in seen_chars:

    What happens: The earlier copy of s[right] can be several places after left. One step removes only s[left], which may be a different letter. Then the window still holds the repeat when it is measured.

    Example: s = "pwwkew"
    Mistake: returns 4Fix: returns 3
  3. 3

    Gotcha 3: Keeping map keys whose count dropped to 0

    Mistake
    count[fruits[left]] -= 1
    Fix
    count[fruits[left]] -= 1
    if count[fruits[left]] == 0:
        del count[fruits[left]]

    What happens: Fruit Into Baskets (LC 904) allows at most 2 fruit types in the window. It keeps a count for each type and checks len(count). When left moves, the count of fruits[left] goes down by 1. A type whose count reaches 0 is no longer in the window. If its key stays, len(count) still counts it, and the window shrinks more than needed.

  4. 4

    Gotcha 4: Using one window to count exactly k distinct values

    What happens: Subarrays with K Different Integers (LC 992) counts the subarrays with exactly k distinct values. For one right, a whole range of starts gives exactly k values. One window tracks only one start, so it misses the others. Count the subarrays with at most k distinct values, and subtract those with at most k - 1. That is the At-Most Trick.

Not this one if…
  • The question asks for the shortest window that meets a requirement, as in Minimum Window Substring (LC 76): adding elements helps the window instead of breaking it. See Shortest Window
  • The window length is fixed by the input, as in Find All Anagrams in a String (LC 438): every window has the size of p, so there is no longest one to find. See Fixed Window

Theory

The question we build

What we know

s is a sequence of characters. A window holds a repeat, or it does not; adding a character can only turn a valid window invalid, never the other way.

What the problem gives us

The input is s. Return the length of the longest substring with no repeated character.

The question we build

For each right, we ask: "does the window s[left..right] hold a repeat?" When it does, which starts can still give a valid window ending at right? A window [j, right] with j < left contains everything [left, right] contains, plus more, so if [left, right] already holds the repeat, [j, right] holds it too.

Narrowing down to the template

Which windows that end at right can still be the answer? If the window [left, right] holds a repeat, every window [j, right] with j < left holds it too, so left only moves right. So once left passes a start, no window from that start, ending anywhere at or after right, can ever become valid again: left never needs to move backward. That is exactly Longest Window: check whether adding right is still safe, repair by moving left forward until it is, then add right and measure.

Step 1: Every window

Check every window (left, right) with a fresh set. Correct: a repeat is caught by set membership. States the contract in one listing: the longest window with no repeated character.

Step 1: Every windowPython
1def length_of_longest_brute(s):
2 n = len(s)
3 best = 0
4 for left in range(n):
5 seen = set()
6 for right in range(left, n):
7 if s[right] in seen:
8 break # A fresh set per left: this window holds a repeat
9 seen.add(s[right])
10 best = max(best, right - left + 1)
11 return best

Cost: O(N^2) time, O(N) extra space.

Step 2: Check and repair

If adding right to the window [left, right - 1] would hold a repeat, adding right to any window [j, right - 1] with j < left would hold it too. So left never needs to move backward: before adding right, check whether it would create a repeat, and while it would, evict from left. Only once the while loop confirms adding right is safe is right actually added, so measuring after the add never counts a window that holds a repeat.

Step 2: Check and repairPython
1def lengthOfLongestSubstring(s: str) -> int:
2 seen_chars = set() # characters currently inside the window s[left..right - 1]
3 left = 0
4 max_len = 0
5 
6 # 1. Repair By Evicting From left:
7 # s[right] in seen_chars asks whether adding s[right] would create a repeat, without changing seen_chars. Every window [j, right] with j < left still holds the same duplicate, so left is the only pointer that can move.
8 for right in range(len(s)):
9 while s[right] in seen_chars:
10 seen_chars.remove(s[left])
11 left += 1
12 
13 # 2. Absorb Index right:
14 # Once the while loop confirms s[right] is not in seen_chars, it is actually added.
15 seen_chars.add(s[right])
16 # 3. Record Only After The Add:
17 max_len = max(max_len, right - left + 1)
18 
19 return max_len

Cost: O(N) time, O(min(N, Sigma)) space, where Sigma is the number of different characters.

Step 3: The named template

The loop does not change between problems. Only add, remove and would_be_valid change.

Step 3: The named templatePython
1+New line: from typing import Callable
2
3+New line: def longest_window(n: int, add: Callable[[int], None], remove: Callable[[int], None], would_be_valid: Callable[[int], bool]) -> int:
4+New line: """Longest Window: the length of the longest valid window [left, right] of indices 0..n-1.
5+New line: Precondition: every shorter window inside a valid window is valid too, and the empty window is valid.
6+New line: add(i) and remove(i) update the window's state for index i.
7+New line: would_be_valid(i) reads whether adding index i to the current window would keep it valid, without changing the window's state.
8+New line: T = the cost of one add, remove or would_be_valid call.
9+New line: Time: O(N * T) | Space: O(1) besides the window's state
10+New line: """
11 left = 0
12 max_len = 0
13
14+New line: for right in range(n):
15+New line: # 1. Repair By Evicting From left:
16+New line: # would_be_valid(right) asks whether adding index right would still be a valid window, without changing the window's state. Every window [j, right] with j < left contains everything [left, right] contains, so if adding right to [left, right] would still be invalid, no later left can keep right out of it; evicting left is the only way back to a window that right can be added to.
17+New line: while not would_be_valid(right):
18+New line: remove(left)
19 left += 1
20 # 2. Absorb Index right:
21+New line: # Once would_be_valid(right) confirms the window stays valid, right is actually added.
22+New line: add(right)
23 # 3. Record Only After The Add:
24+New line: # The window [left, right] is now valid and fully updated, so its length can be measured.
25 max_len = max(max_len, right - left + 1)
26
27 return max_len

Cost: O(N * T), where T is the cost of one add, remove or would_be_valid call.

BeforeAfterWhat changed
while s[right] in seen_chars: seen_chars.remove(s[left]); left += 1while not would_be_valid(right): remove(left); left += 1The repeat check becomes would_be_valid(right), which reads whether adding right would still be valid without changing the window, and the eviction becomes remove.
seen_chars.add(s[right])add(right)Absorbing the incoming character becomes add, still called only after would_be_valid(right) confirms the window stays valid.
max_len = max(max_len, right - left + 1)max_len = max(max_len, right - left + 1)Identical, still after the add.

Practice

Canonical LeetCode:Longest Substring Without Repeating Characters (LC 3)Fruit Into Baskets (LC 904)Longest Subarray of 1's After Deleting One Element (LC 1493)Longest Substring with At Most K Distinct Characters (LC 340)Max Consecutive Ones II (LC 487)Minimum Operations to Reduce X to Zero (LC 1658)Longest Substring with At Most Two Distinct Characters (LC 159)Longest Repeating Character Replacement (LC 424)Max Consecutive Ones III (LC 1004)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•