docs(docs): add longest‑substring problem page and sliding‑window reference with SVG illustrations

This commit is contained in:
Prad Nukala
2026-08-25 13:18:32 -04:00
parent fd0d6c6ea5
commit bc2269d9dd
5 changed files with 336 additions and 0 deletions
@@ -0,0 +1,47 @@
---
title: '3. Longest Substring Without Repeating Characters'
description: Given a string s, find the length of the longest substring without duplicate characters
sidebar:
label: 'Longest Substring Without Repeating Characters'
badge: 'Medium'
---
<Badge variant="accent">Sliding Window</Badge>
### Example 1:
- Input: `s = "abcabcbb"`
- Output: `3`
- Explanation: The answer is "abc", with the length of `3`. Note that "bca" and "cab" are also correct answers.
### Example 2:
- Input: `s = "bbbbb"`
- Output: `1`
- Explanation: The answer is "b", with the length of `1`.
### Example 3:
- Input: `s = "pwwkew"`
- Output: `3`
- Explanation: The answer is "wke", with the length of `3`. Notice that the answer must be a substring, "pwke" is a subsequence and not a substring.
### Constraints:
- `0 <= s.length <= 10^5`
- `s` consists of English letters, digits, symbols and spaces.
## Solution
```py
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
left = 0
ans = 0
seen = set()
for right, c in enumerate(s):
while c in seen:
seen.remove(s[left])
left += 1
seen.add(c)
ans = max(ans, right - left + 1)
return ans
```
@@ -0,0 +1,103 @@
---
title: Sliding Window
description: 'Sliding window is a specialized two-pointer family where the pointers define a contiguous interval and you maintain information about that entire interval as it moves.'
---
:::warning
This is the family where invariants matter the most
:::
## What Makes Sliding Window Different?
Many problems have two moving boundaries. That alone does not make them sliding window problems.
A problem belongs to this family when:
- the answer depends on the subarray or substring between left and right
- you can update the window state incrementally as elements enter or leave
- you maintain a condition that tells you whether the window is valid
## What is an Invariant?
An invariant is a statement that stays true before and after every iteration.
For sliding window, the invariant often looks like one of these:
- "the current window is valid"
- "the current window contains no duplicates"
- "the current window sum is at most the target"
- "the frequency map matches the characters currently inside the window"
The invariant gives the loop its structure:
- expand the window by moving right
- update the window state
- if the invariant breaks, shrink from the left until it holds again
- update the answer only when you know the invariant is satisfied
## Why Invariants Matter
Without an invariant, shrinking the window becomes arbitrary. You no longer know:
- when the window is valid
- when to update the answer
- why the left pointer should stop moving
- The invariant is the reason the algorithm is correct.
## The Main Subtypes
### 1. Fixed-size windows
:::tip
The window size never changes. The main task is updating the window state efficiently when one element enters and one leaves.
:::
![Sliding Window Fixed Size](../../public/sliding-window-fixed-size.svg)
### 2. Flexible-size windows with a validity rule
:::tip
Here the window expands until invalid, then shrinks until valid again.
:::
![Flexible-size windows](../../public/sliding-window-flexible.svg)
### 3. Frequency-tracking windows
:::tip
The difficulty comes from keeping the bookkeeping correct as characters enter and leave.
:::
![Frequency-tracking windows](../../public/sliding-window-frequency.svg)
## How Sliding Window Differs From Other Two-Pointer Families
Compare it with Container With Most Water:
- In sliding window, the interval itself has meaning.
- In container, the interval is just the remaining search space.
Compare it with Remove Duplicates:
- In sliding window, both pointers describe a live window.
- In compaction, the left part of the array is already the answer prefix.
## A Practical Template
A common variable-size sliding window has this shape:
1. Add the new rightmost element.
2. Update the window state.
3. While the invariant is broken, remove elements from the left.
4. Once the invariant is restored, update the answer.
The exact data structure changes from problem to problem, but the logic stays the same.
As you work through this section, keep asking:
:::warning
What invariant does my window maintain, and at what moment is it safe to update the answer?
:::
@@ -0,0 +1,66 @@
<svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg">
<!-- Background (REQUIRED) -->
<rect width="800" height="450" fill="#f8f9fa" rx="10"/>
<!-- Title and Subtitle -->
<text x="400" y="45" font-family="Arial, sans-serif" font-size="26" font-weight="bold" fill="#2c3e50" text-anchor="middle">Sliding Window: Fixed Size</text>
<text x="400" y="75" font-family="Arial, sans-serif" font-size="16" fill="#7f8c8d" text-anchor="middle">Window of size K=3 moving through the array.</text>
<!-- Step 1: Initial window -->
<g transform="translate(150, 130)">
<text x="0" y="-20" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">1. Current window (Sum = 10)</text>
<!-- Array -->
<g transform="translate(0, 20)">
<rect x="0" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="30" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">2</text>
<rect x="70" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="100" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">3</text>
<rect x="140" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="170" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">5</text>
<rect x="210" y="0" width="60" height="50" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="3"/>
<text x="240" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">1</text>
<rect x="280" y="0" width="60" height="50" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="3"/>
<text x="310" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">8</text>
<!-- Bracket -->
<path d="M 0,60 L 0,70 L 200,70 L 200,60" fill="none" stroke="#10b981" stroke-width="2"/>
</g>
</g>
<!-- Transition -->
<g transform="translate(400, 260)">
<path d="M 0,0 L 0,30" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="10" y="20" font-family="Arial, sans-serif" font-size="14" font-style="italic" fill="#7f8c8d">Slide window: -2, +1</text>
</g>
<!-- Step 2: Slid window -->
<g transform="translate(150, 340)">
<text x="0" y="-20" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">2. Next window (Sum = 9)</text>
<!-- Array -->
<g transform="translate(0, 20)">
<rect x="0" y="0" width="60" height="50" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2" rx="3"/>
<text x="30" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#94a3b8">2</text>
<rect x="70" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="100" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">3</text>
<rect x="140" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="170" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">5</text>
<rect x="210" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="240" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">1</text>
<rect x="280" y="0" width="60" height="50" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="3"/>
<text x="310" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">8</text>
<!-- Bracket -->
<path d="M 70,60 L 70,70 L 270,70 L 270,60" fill="none" stroke="#10b981" stroke-width="2"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -0,0 +1,66 @@
<svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg">
<!-- Background (REQUIRED) -->
<rect width="800" height="450" fill="#f8f9fa" rx="10"/>
<!-- Title and Subtitle -->
<text x="400" y="45" font-family="Arial, sans-serif" font-size="26" font-weight="bold" fill="#2c3e50" text-anchor="middle">Sliding Window: Flexible Size</text>
<text x="400" y="75" font-family="Arial, sans-serif" font-size="16" fill="#7f8c8d" text-anchor="middle">Window expands until invalid, then shrinks until valid again.</text>
<!-- Step 1: Invalid state -->
<g transform="translate(150, 130)">
<text x="0" y="-20" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">1. Expansion breaks invariant</text>
<!-- Array -->
<g transform="translate(0, 20)">
<rect x="0" y="0" width="60" height="50" fill="#fee2e2" stroke="#ef4444" stroke-width="3" rx="3"/>
<text x="30" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">a</text>
<polygon points="30,60 25,70 35,70" fill="#27ae60"/>
<text x="30" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#27ae60" text-anchor="middle">L</text>
<rect x="70" y="0" width="60" height="50" fill="#fee2e2" stroke="#ef4444" stroke-width="3" rx="3"/>
<text x="100" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">b</text>
<rect x="140" y="0" width="60" height="50" fill="#fee2e2" stroke="#ef4444" stroke-width="3" rx="3"/>
<text x="170" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">c</text>
<rect x="210" y="0" width="60" height="50" fill="#fee2e2" stroke="#ef4444" stroke-width="4" rx="3"/>
<text x="240" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">a</text>
<polygon points="240,60 235,70 245,70" fill="#3498db"/>
<text x="240" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">R</text>
<text x="300" y="32" font-family="Arial, sans-serif" font-size="14" fill="#ef4444" font-weight="bold">Duplicate 'a'!</text>
</g>
</g>
<!-- Transition -->
<g transform="translate(400, 260)">
<path d="M 0,0 L 0,30" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="10" y="20" font-family="Arial, sans-serif" font-size="14" font-style="italic" fill="#7f8c8d">Shrink from left</text>
</g>
<!-- Step 2: Valid again -->
<g transform="translate(150, 340)">
<text x="0" y="-20" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">2. Invariant restored</text>
<!-- Array -->
<g transform="translate(0, 20)">
<rect x="0" y="0" width="60" height="50" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2" rx="3"/>
<text x="30" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#94a3b8">a</text>
<rect x="70" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="100" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">b</text>
<polygon points="100,60 95,70 105,70" fill="#27ae60"/>
<text x="100" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#27ae60" text-anchor="middle">L</text>
<rect x="140" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="170" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">c</text>
<rect x="210" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="240" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">a</text>
<polygon points="240,60 235,70 245,70" fill="#3498db"/>
<text x="240" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">R</text>
<text x="300" y="32" font-family="Arial, sans-serif" font-size="14" fill="#10b981" font-weight="bold">Valid again!</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

@@ -0,0 +1,54 @@
<svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg">
<!-- Background (REQUIRED) -->
<rect width="800" height="450" fill="#f8f9fa" rx="10"/>
<!-- Title and Subtitle -->
<text x="400" y="45" font-family="Arial, sans-serif" font-size="26" font-weight="bold" fill="#2c3e50" text-anchor="middle">Sliding Window: Frequency Tracking</text>
<text x="400" y="75" font-family="Arial, sans-serif" font-size="16" fill="#7f8c8d" text-anchor="middle">Maintaining a character frequency map for the current window.</text>
<!-- Left: Window -->
<g transform="translate(100, 150)">
<text x="0" y="-30" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">Current Window</text>
<!-- Array -->
<g transform="translate(0, 20)">
<rect x="0" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="30" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">a</text>
<polygon points="30,60 25,70 35,70" fill="#27ae60"/>
<text x="30" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#27ae60" text-anchor="middle">L</text>
<rect x="70" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="100" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">b</text>
<rect x="140" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="3" rx="3"/>
<text x="170" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">a</text>
<polygon points="170,60 165,70 175,70" fill="#3498db"/>
<text x="170" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">R</text>
<rect x="210" y="0" width="60" height="50" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="3"/>
<text x="240" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">c</text>
</g>
</g>
<!-- Right: Frequency Map -->
<g transform="translate(500, 150)">
<text x="0" y="-30" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">Frequency Map</text>
<!-- Map Container -->
<rect x="0" y="0" width="150" height="120" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="8"/>
<!-- Entries -->
<g transform="translate(25, 35)">
<text x="0" y="0" font-family="monospace" font-size="18" fill="#2c3e50">'a' : 2</text>
<text x="0" y="30" font-family="monospace" font-size="18" fill="#2c3e50">'b' : 1</text>
<text x="0" y="60" font-family="monospace" font-size="18" fill="#7f8c8d">'c' : 0</text>
</g>
<!-- Invariant label -->
<rect x="-20" y="140" width="190" height="40" fill="#e3f2fd" stroke="#3498db" stroke-width="1" rx="5"/>
<text x="75" y="165" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#1565c0" text-anchor="middle">INVARIANT: SYNCED STATE</text>
</g>
<!-- Connector -->
<path d="M 380,195 L 480,195" stroke="#dee2e6" stroke-width="2" stroke-dasharray="5,5" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB