feat(patterns): pattern taxonomy + is_optimal

This commit is contained in:
2025-09-08 16:03:14 +01:00
parent 5b768f6a21
commit 13bab63618
28 changed files with 1434 additions and 26 deletions

View File

@@ -1,6 +1,8 @@
name: Backtracking
slug: backtracking
difficulty_level: 3
pattern_type: algorithm
display_order: 10
description: >
Build solutions incrementally, exploring choices one at a time and abandoning

View File

@@ -1,6 +1,8 @@
name: BFS (Breadth-First Search)
slug: bfs
difficulty_level: 3
pattern_type: algorithm
display_order: 6
description: >
Level-by-level traversal using a queue, exploring all neighbors at the current

View File

@@ -1,6 +1,8 @@
name: Binary Search
slug: binary-search
difficulty_level: 2
pattern_type: algorithm
display_order: 4
description: >
Efficiently search sorted data by repeatedly dividing the search space in half.
@@ -200,3 +202,172 @@ related_patterns:
- two-pointers
prerequisite_patterns: []
visualization_examples:
- id: binary-search-find-target
title: Find Target in Sorted Array
input:
nums: [1, 3, 5, 7, 9, 11, 13]
target: 9
code: |
def binary_search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
steps:
- id: step-1
description: "Initialize pointers: left=0, right=6. Search space is entire array."
structures:
nums:
type: array
values:
- { value: 1, state: default }
- { value: 3, state: default }
- { value: 5, state: default }
- { value: 7, state: default }
- { value: 9, state: default }
- { value: 11, state: default }
- { value: 13, state: default }
pointers:
left: 0
right: 6
variables:
target: 9
left: 0
right: 6
codeHighlight:
startLine: 2
endLine: 2
- id: step-2
description: "Calculate mid=3. nums[3]=7 < target=9, so search right half."
structures:
nums:
type: array
values:
- { value: 1, state: visited }
- { value: 3, state: visited }
- { value: 5, state: visited }
- { value: 7, state: comparing }
- { value: 9, state: default }
- { value: 11, state: default }
- { value: 13, state: default }
pointers:
left: 0
mid: 3
right: 6
variables:
target: 9
left: 0
right: 6
mid: 3
codeHighlight:
startLine: 9
endLine: 10
- id: step-3
description: "Update left=4. New search space is indices 4-6."
structures:
nums:
type: array
values:
- { value: 1, state: visited }
- { value: 3, state: visited }
- { value: 5, state: visited }
- { value: 7, state: visited }
- { value: 9, state: default }
- { value: 11, state: default }
- { value: 13, state: default }
pointers:
left: 4
right: 6
variables:
target: 9
left: 4
right: 6
codeHighlight:
startLine: 4
endLine: 4
- id: step-4
description: "Calculate mid=5. nums[5]=11 > target=9, so search left half."
structures:
nums:
type: array
values:
- { value: 1, state: visited }
- { value: 3, state: visited }
- { value: 5, state: visited }
- { value: 7, state: visited }
- { value: 9, state: default }
- { value: 11, state: comparing }
- { value: 13, state: visited }
pointers:
left: 4
mid: 5
right: 6
variables:
target: 9
left: 4
right: 6
mid: 5
codeHighlight:
startLine: 11
endLine: 12
- id: step-5
description: "Update right=4. Search space narrowed to index 4 only."
structures:
nums:
type: array
values:
- { value: 1, state: visited }
- { value: 3, state: visited }
- { value: 5, state: visited }
- { value: 7, state: visited }
- { value: 9, state: active }
- { value: 11, state: visited }
- { value: 13, state: visited }
pointers:
left: 4
right: 4
variables:
target: 9
left: 4
right: 4
codeHighlight:
startLine: 4
endLine: 4
- id: step-6
description: "Calculate mid=4. nums[4]=9 == target=9. Found at index 4!"
structures:
nums:
type: array
values:
- { value: 1, state: visited }
- { value: 3, state: visited }
- { value: 5, state: visited }
- { value: 7, state: visited }
- { value: 9, state: found }
- { value: 11, state: visited }
- { value: 13, state: visited }
pointers:
mid: 4
variables:
target: 9
mid: 4
result: 4
codeHighlight:
startLine: 7
endLine: 8

View File

@@ -1,6 +1,8 @@
name: Bit Manipulation
slug: bit-manipulation
difficulty_level: 2
pattern_type: technique
display_order: 19
description: >
Techniques using binary operations (AND, OR, XOR, NOT, shifts) to solve

View File

@@ -0,0 +1,92 @@
name: Counting / Bucket Sort
slug: counting-sort
difficulty_level: 2
pattern_type: technique
display_order: 20
description: >
Exploit bounded value ranges to achieve linear time sorting or selection
by using values as array indices.
when_to_use: |
- Finding top K elements when frequencies are bounded
- Sorting when values are in a known, limited range
- Problems involving frequency counting with bounded inputs
- Color sorting (Dutch National Flag)
metaphor: |
Imagine sorting mail into numbered PO boxes. Instead of comparing letters
to each other, you simply look at the box number and drop it in. If you
have 100 boxes, sorting 1000 letters takes 1000 steps, not 1000 x log(1000).
core_concept: |
When values are bounded within a known range [0, k], you can use the value
itself as an index into an array of "buckets." This converts comparison-based
O(n log n) sorting into O(n + k) counting operations.
The key insight: **bounded values = direct addressing is possible**.
code_template: |
def bucket_sort_approach(nums: list[int], k: int) -> list[int]:
# Create buckets indexed by value/frequency
n = len(nums)
buckets = [[] for _ in range(n + 1)] # n+1 for frequency 0 to n
# Count frequencies
count = {}
for num in nums:
count[num] = count.get(num, 0) + 1
# Place elements in frequency buckets
for num, freq in count.items():
buckets[freq].append(num)
# Collect from highest frequency
result = []
for i in range(n, 0, -1):
for num in buckets[i]:
result.append(num)
if len(result) == k:
return result
return result
recognition_signals:
- "top k frequent"
- "sort colors"
- "values in range [0, n]"
- "frequency bounded by array size"
- "O(n) time required"
- "counting occurrences"
common_mistakes:
- title: Using Heap When Bucket Sort is Optimal
description: |
Heap gives O(n log k) but bucket sort gives O(n) when frequencies
are bounded. Always check if values/frequencies have a known upper bound.
fix: |
Ask: "What's the maximum possible value/frequency?" If bounded by n,
use bucket sort.
- title: Off-by-One in Bucket Array
description: |
Creating `n` buckets for frequencies 0 to n-1 misses frequency `n`
(when all elements are identical).
fix: |
Create `n + 1` buckets to handle frequencies from 0 to n inclusive.
variations:
- name: Top K Frequent Elements
description: Use frequency as bucket index, collect from highest
example: "top-k-frequent-elements"
- name: Sort Colors (Dutch National Flag)
description: Three buckets for 0, 1, 2
example: "sort-colors"
- name: H-Index
description: Citation count buckets
example: "h-index"
related_patterns:
- heap
- two-pointers
prerequisite_patterns: []

View File

@@ -0,0 +1,96 @@
name: Cyclic Sort
slug: cyclic-sort
difficulty_level: 2
pattern_type: algorithm
display_order: 21
description: >
Place each number at its "correct" index when dealing with arrays containing
numbers in the range [1, n] or [0, n-1].
when_to_use: |
- Array contains numbers from 1 to n (or 0 to n-1)
- Finding missing/duplicate numbers in such arrays
- Problems asking for O(1) space and O(n) time
- "First missing positive" type problems
metaphor: |
Imagine a row of n chairs numbered 1 to n, and n people each holding a
ticket with their seat number. Instead of searching for each person's seat,
you ask everyone to swap positions until they're in their ticketed seat.
After at most n swaps, everyone is seated correctly.
core_concept: |
For an array where values should map directly to indices (e.g., value 3
belongs at index 2 if 1-indexed), repeatedly swap each element to its
correct position until the array is "sorted."
Key insight: Each swap places at least one element correctly, so at most
n swaps are needed -> O(n) time with O(1) space.
code_template: |
def cyclic_sort(nums: list[int]) -> list[int]:
"""Sort array where values are in range [1, n]."""
i = 0
while i < len(nums):
# Correct index for value nums[i] (1-indexed value -> 0-indexed position)
correct_idx = nums[i] - 1
# If not in correct position and not a duplicate, swap
if nums[i] != nums[correct_idx]:
nums[i], nums[correct_idx] = nums[correct_idx], nums[i]
else:
i += 1
return nums
def find_missing(nums: list[int]) -> int:
"""Find missing number after cyclic sort."""
cyclic_sort(nums)
for i, num in enumerate(nums):
if num != i + 1:
return i + 1
return len(nums) + 1
recognition_signals:
- "array of length n with values 1 to n"
- "find the missing number"
- "find the duplicate"
- "first missing positive"
- "O(1) extra space"
- "in-place rearrangement"
common_mistakes:
- title: Infinite Loop on Duplicates
description: |
Swapping endlessly when current value equals value at target index
(both are duplicates).
fix: |
Check `nums[i] != nums[correct_idx]` before swapping, not just
`nums[i] != correct_idx + 1`.
- title: Wrong Index Calculation
description: |
Confusing 0-indexed vs 1-indexed. If values are 1 to n, correct
index is `value - 1`. If values are 0 to n-1, correct index equals value.
fix: |
Clearly define the mapping: `correct_idx = nums[i] - 1` for 1-indexed values.
variations:
- name: Find Missing Number
description: After cyclic sort, scan for index where value != index + 1
example: "missing-number"
- name: Find Duplicate
description: After cyclic sort, find where value != expected
example: "find-the-duplicate-number"
- name: First Missing Positive
description: Ignore negatives and values > n, then cyclic sort
example: "first-missing-positive"
- name: Find All Missing Numbers
description: Collect all indices where value != expected
example: "find-all-numbers-disappeared-in-an-array"
related_patterns:
- counting-sort
- fast-slow-pointers
prerequisite_patterns: []

View File

@@ -1,6 +1,8 @@
name: DFS (Depth-First Search)
slug: dfs
difficulty_level: 3
pattern_type: algorithm
display_order: 7
description: >
Explore as deep as possible along each branch before backtracking, using

View File

@@ -0,0 +1,97 @@
name: Divide and Conquer
slug: divide-and-conquer
difficulty_level: 3
pattern_type: algorithm
display_order: 23
description: >
Break a problem into smaller subproblems, solve them independently,
and combine results. Unlike DP, subproblems don't overlap.
when_to_use: |
- Problems naturally split into independent halves
- Merge sort style combining
- Finding kth element efficiently
- Tree-structured recursion without memoization needs
metaphor: |
Imagine sorting a deck of cards by splitting it in half, having two
friends each sort their half, then merging the sorted halves. Each
friend can recursively split their half too. The key: halves are
independent and combining sorted halves is easy.
core_concept: |
Three steps:
1. **Divide:** Split problem into smaller subproblems
2. **Conquer:** Solve subproblems recursively (base case when trivial)
3. **Combine:** Merge subproblem solutions into final answer
Time complexity often follows: T(n) = aT(n/b) + O(n^c)
Solved by Master Theorem.
code_template: |
def merge_sort(arr: list[int]) -> list[int]:
"""Classic divide and conquer example."""
if len(arr) <= 1:
return arr
# Divide
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
# Combine (merge)
return merge(left, right)
def merge(left: list[int], right: list[int]) -> list[int]:
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
recognition_signals:
- "merge sort"
- "find kth largest/smallest"
- "count inversions"
- "closest pair of points"
- "problems on sorted arrays that can be split"
common_mistakes:
- title: Confusing with Dynamic Programming
description: |
D&C subproblems are independent. DP subproblems overlap and need
memoization. Using D&C on overlapping subproblems causes exponential time.
fix: |
Check: Do subproblems share computation? If yes, use DP. If no, use D&C.
- title: Inefficient Combine Step
description: |
If combining takes O(n^2), overall might not be better than brute force.
fix: |
Design O(n) or O(n log n) combine step. Merge sort's merge is O(n).
variations:
- name: Merge Sort
description: Sort by splitting, sorting halves, merging
example: "sort-an-array"
- name: Quick Select
description: Find kth element by partitioning
example: "kth-largest-element-in-an-array"
- name: Count Inversions
description: Count pairs where larger element precedes smaller
example: "count-of-smaller-numbers-after-self"
related_patterns:
- binary-search
- dynamic-programming
prerequisite_patterns:
- binary-search

View File

@@ -1,6 +1,8 @@
name: Dynamic Programming
slug: dynamic-programming
difficulty_level: 4
pattern_type: technique
display_order: 11
description: >
Break problems into overlapping subproblems, storing results to avoid

View File

@@ -1,6 +1,8 @@
name: Fast & Slow Pointers
slug: fast-slow-pointers
difficulty_level: 2
pattern_type: algorithm
display_order: 3
description: >
Use two pointers moving at different speeds to detect cycles, find midpoints,
@@ -296,3 +298,188 @@ related_patterns:
prerequisite_patterns:
- two-pointers
visualization_examples:
- id: find-middle-of-list
title: Find Middle of Linked List
input:
list: [1, 2, 3, 4, 5]
code: |
def find_middle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
steps:
- id: step-1
description: "Initialize slow and fast pointers at head (node 1)."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: active }
- { value: 2, state: default }
- { value: 3, state: default }
- { value: 4, state: default }
- { value: 5, state: default }
pointers:
slow: 0
fast: 0
variables: {}
codeHighlight:
startLine: 2
endLine: 2
- id: step-2
description: "Move slow 1 step (to 2), fast 2 steps (to 3)."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: visited }
- { value: 2, state: active }
- { value: 3, state: active }
- { value: 4, state: default }
- { value: 5, state: default }
pointers:
slow: 1
fast: 2
variables: {}
codeHighlight:
startLine: 5
endLine: 6
- id: step-3
description: "Move slow 1 step (to 3), fast 2 steps (to 5)."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: visited }
- { value: 2, state: visited }
- { value: 3, state: active }
- { value: 4, state: visited }
- { value: 5, state: active }
pointers:
slow: 2
fast: 4
variables: {}
codeHighlight:
startLine: 5
endLine: 6
- id: step-4
description: "fast.next is null, loop ends. slow is at middle (node 3)."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: visited }
- { value: 2, state: visited }
- { value: 3, state: found }
- { value: 4, state: visited }
- { value: 5, state: visited }
pointers:
slow: 2
variables:
result: 3
codeHighlight:
startLine: 8
endLine: 8
- id: detect-cycle
title: Detect Cycle in Linked List
input:
list: [1, 2, 3, 4, 5]
cycle_at: 2
code: |
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
steps:
- id: step-1
description: "List has a cycle: 5 points back to 3. Initialize pointers at head."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: active }
- { value: 2, state: default }
- { value: 3, state: default, annotations: ["cycle start"] }
- { value: 4, state: default }
- { value: 5, state: default, annotations: ["→3"] }
pointers:
slow: 0
fast: 0
variables: {}
codeHighlight:
startLine: 2
endLine: 2
- id: step-2
description: "slow moves to 2, fast moves to 3."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: visited }
- { value: 2, state: active }
- { value: 3, state: active, annotations: ["cycle start"] }
- { value: 4, state: default }
- { value: 5, state: default, annotations: ["→3"] }
pointers:
slow: 1
fast: 2
variables: {}
codeHighlight:
startLine: 5
endLine: 6
- id: step-3
description: "slow moves to 3, fast moves to 5."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: visited }
- { value: 2, state: visited }
- { value: 3, state: active, annotations: ["cycle start"] }
- { value: 4, state: visited }
- { value: 5, state: active, annotations: ["→3"] }
pointers:
slow: 2
fast: 4
variables: {}
codeHighlight:
startLine: 5
endLine: 6
- id: step-4
description: "slow moves to 4, fast loops back to 4 (via 5→3→4). They meet!"
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: visited }
- { value: 2, state: visited }
- { value: 3, state: visited, annotations: ["cycle start"] }
- { value: 4, state: found, annotations: ["meeting point"] }
- { value: 5, state: visited, annotations: ["→3"] }
pointers:
"slow, fast": 3
variables:
cycle_detected: true
codeHighlight:
startLine: 8
endLine: 9

View File

@@ -1,6 +1,8 @@
name: Greedy
slug: greedy
difficulty_level: 3
pattern_type: technique
display_order: 12
description: >
Make locally optimal choices at each step, hoping to find a global optimum.

View File

@@ -1,6 +1,8 @@
name: Heap / Priority Queue
slug: heap
difficulty_level: 3
pattern_type: data_structure
display_order: 16
description: >
A data structure that efficiently maintains the minimum or maximum element,

View File

@@ -1,6 +1,8 @@
name: Overlapping Intervals
slug: intervals
difficulty_level: 2
pattern_type: technique
display_order: 13
description: >
Process and manipulate intervals (ranges) that may share common regions.

View File

@@ -1,6 +1,8 @@
name: LinkedList In-Place Reversal
slug: linkedlist-reversal
difficulty_level: 2
pattern_type: technique
display_order: 14
description: >
Reverse linked list nodes in-place by manipulating pointers without allocating
@@ -277,3 +279,130 @@ related_patterns:
- two-pointers
prerequisite_patterns: []
visualization_examples:
- id: reverse-linked-list
title: Reverse Entire Linked List
input:
list: [1, 2, 3, 4]
code: |
def reverse_list(head):
prev = None
curr = head
while curr:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return prev
steps:
- id: step-1
description: "Initialize prev=None, curr=head (node 1)."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: active }
- { value: 2, state: default }
- { value: 3, state: default }
- { value: 4, state: default }
pointers:
curr: 0
variables:
prev: "null"
codeHighlight:
startLine: 2
endLine: 3
- id: step-2
description: "Save next=2, reverse link: 1→null. Move pointers forward."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: visited, annotations: ["→null"] }
- { value: 2, state: active }
- { value: 3, state: default }
- { value: 4, state: default }
pointers:
prev: 0
curr: 1
variables:
next_node: 2
codeHighlight:
startLine: 6
endLine: 9
- id: step-3
description: "Save next=3, reverse link: 2→1. Move pointers forward."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: visited, annotations: ["→null"] }
- { value: 2, state: visited, annotations: ["→1"] }
- { value: 3, state: active }
- { value: 4, state: default }
pointers:
prev: 1
curr: 2
variables:
next_node: 3
codeHighlight:
startLine: 6
endLine: 9
- id: step-4
description: "Save next=4, reverse link: 3→2. Move pointers forward."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: visited, annotations: ["→null"] }
- { value: 2, state: visited, annotations: ["→1"] }
- { value: 3, state: visited, annotations: ["→2"] }
- { value: 4, state: active }
pointers:
prev: 2
curr: 3
variables:
next_node: 4
codeHighlight:
startLine: 6
endLine: 9
- id: step-5
description: "Save next=null, reverse link: 4→3. curr becomes null, loop ends."
structures:
list:
type: linkedlist
nodes:
- { value: 1, state: visited, annotations: ["→null"] }
- { value: 2, state: visited, annotations: ["→1"] }
- { value: 3, state: visited, annotations: ["→2"] }
- { value: 4, state: found, annotations: ["→3", "new head"] }
pointers:
prev: 3
variables:
next_node: "null"
codeHighlight:
startLine: 11
endLine: 11
- id: step-6
description: "Return prev (node 4). Reversed list: 4→3→2→1→null"
structures:
reversed:
type: linkedlist
nodes:
- { value: 4, state: found, annotations: ["head"] }
- { value: 3, state: default }
- { value: 2, state: default }
- { value: 1, state: default }
variables:
result: "4→3→2→1→null"
codeHighlight:
startLine: 11
endLine: 11

View File

@@ -1,6 +1,8 @@
name: Matrix Traversal
slug: matrix-traversal
difficulty_level: 3
pattern_type: algorithm
display_order: 9
description: >
Navigate 2D grids systematically using DFS, BFS, or directional iteration.

View File

@@ -1,6 +1,8 @@
name: Monotonic Stack
slug: monotonic-stack
difficulty_level: 3
pattern_type: data_structure
display_order: 15
description: >
Maintain a stack where elements are always in sorted order (either increasing or
@@ -267,3 +269,245 @@ related_patterns:
- sliding-window
prerequisite_patterns: []
visualization_examples:
- id: largest-rectangle-histogram
title: "Largest Rectangle in Histogram"
input:
heights: [2, 1, 5, 6, 2, 3]
code: |
def largest_rectangle(heights):
stack = [] # (index, height)
max_area = 0
for i, h in enumerate(heights):
start = i
while stack and stack[-1][1] > h:
idx, height = stack.pop()
max_area = max(max_area, height * (i - idx))
start = idx
stack.append((start, h))
for idx, height in stack:
max_area = max(max_area, height * (len(heights) - idx))
return max_area
steps:
- id: step-1
description: "Goal: Find the largest rectangle that fits in this histogram. Each bar's height is shown, and we want to find the biggest rectangular area."
structures:
heights:
type: histogram
bars:
- { value: 2, state: default }
- { value: 1, state: default }
- { value: 5, state: default }
- { value: 6, state: default }
- { value: 2, state: default }
- { value: 3, state: default }
variables:
question: "What's the largest rectangle?"
max_area: 0
- id: step-2
description: "We scan left to right. Bar 0 (height 2) could be the shortest bar in some rectangle. Push it to stack. Format: h:height @start_index."
structures:
heights:
type: histogram
bars:
- { value: 2, state: active }
- { value: 1, state: default }
- { value: 5, state: default }
- { value: 6, state: default }
- { value: 2, state: default }
- { value: 3, state: default }
pointers:
i: 0
stack (h:height @start):
type: stack
values:
- { value: "h:2 @0", state: active }
variables:
max_area: 0
- id: step-3
description: "Bar 1 (height 1) is SHORTER than bar 0 (height 2). Bar 0's rectangle can't extend further right! Pop it and calculate: width=1, area=2x1=2."
structures:
heights:
type: histogram
bars:
- { value: 2, state: comparing }
- { value: 1, state: active }
- { value: 5, state: default }
- { value: 6, state: default }
- { value: 2, state: default }
- { value: 3, state: default }
pointers:
i: 1
rectangle:
startIndex: 0
endIndex: 0
height: 2
state: comparing
label: "Area: 2"
stack (h:height @start):
type: stack
values: []
variables:
max_area: 2
calculation: "height(2) × width(1) = 2"
- id: step-4
description: "Now push bar 1 (height 1). Since bar 0 was popped, bar 1's rectangle can extend back to index 0. Stack stores (0, 1)."
structures:
heights:
type: histogram
bars:
- { value: 2, state: visited }
- { value: 1, state: active }
- { value: 5, state: default }
- { value: 6, state: default }
- { value: 2, state: default }
- { value: 3, state: default }
pointers:
i: 1
maxArea: 2
stack (h:height @start):
type: stack
values:
- { value: "h:1 @0", state: active }
variables:
max_area: 2
insight: "Height 1 can extend back to index 0!"
- id: step-5
description: "Bar 2 (height 5) is taller than bar 1. Taller bars might extend further right, so push it. Bar 3 (height 6) is also taller — push it too."
structures:
heights:
type: histogram
bars:
- { value: 2, state: visited }
- { value: 1, state: default }
- { value: 5, state: active }
- { value: 6, state: active }
- { value: 2, state: default }
- { value: 3, state: default }
pointers:
i: 3
maxArea: 2
stack (h:height @start):
type: stack
values:
- { value: "h:6 @3", state: active }
- { value: "h:5 @2", state: active }
- { value: "h:1 @0", state: default }
variables:
max_area: 2
stack_note: "Stack keeps increasing heights (bottom to top)"
- id: step-6
description: "Bar 4 (height 2) is SHORTER than bar 3 (height 6). Pop bar 3: width=1, area=6x1=6. Still shorter than bar 2 (height 5), pop it: width=2, area=5x2=10!"
structures:
heights:
type: histogram
bars:
- { value: 2, state: visited }
- { value: 1, state: default }
- { value: 5, state: comparing }
- { value: 6, state: comparing }
- { value: 2, state: active }
- { value: 3, state: default }
pointers:
i: 4
rectangle:
startIndex: 2
endIndex: 3
height: 5
state: found
label: "Area: 10"
maxArea: 10
stack (h:height @start):
type: stack
values:
- { value: "h:1 @0", state: default }
variables:
max_area: 10
calculation: "height(5) × width(2) = 10"
- id: step-7
description: "Push bar 4 at index 2 (it extends back to where bar 2 was). Bar 5 (height 3) is taller, so push it too."
structures:
heights:
type: histogram
bars:
- { value: 2, state: visited }
- { value: 1, state: default }
- { value: 5, state: visited }
- { value: 6, state: visited }
- { value: 2, state: active }
- { value: 3, state: active }
pointers:
i: 5
maxArea: 10
stack (h:height @start):
type: stack
values:
- { value: "h:3 @5", state: active }
- { value: "h:2 @2", state: active }
- { value: "h:1 @0", state: default }
variables:
max_area: 10
note: "h:2 starts @2 because it extends back"
- id: step-8
description: "Done scanning! Process remaining stack: bar 5 (height 3) extends from index 5 to end (width 1), area=3. Bar 4 (height 2) extends from 2 to end (width 4), area=8."
structures:
heights:
type: histogram
bars:
- { value: 2, state: visited }
- { value: 1, state: visited }
- { value: 5, state: visited }
- { value: 6, state: visited }
- { value: 2, state: comparing }
- { value: 3, state: comparing }
rectangle:
startIndex: 2
endIndex: 5
height: 2
state: comparing
label: "Area: 8"
maxArea: 10
stack (h:height @start):
type: stack
values:
- { value: "h:1 @0", state: default }
variables:
max_area: 10
remaining: "Still processing stack..."
- id: step-9
description: "Bar 1 (height 1) extends from index 0 to end (width 6), area=6. The largest rectangle has area 10 — the one spanning bars 2-3 with height 5!"
structures:
heights:
type: histogram
bars:
- { value: 2, state: visited }
- { value: 1, state: visited }
- { value: 5, state: found }
- { value: 6, state: found }
- { value: 2, state: visited }
- { value: 3, state: visited }
rectangle:
startIndex: 2
endIndex: 3
height: 5
state: found
label: "Max: 10"
maxArea: 10
stack (h:height @start):
type: stack
values: []
variables:
max_area: 10
answer: "Largest rectangle = 10"

View File

@@ -1,6 +1,8 @@
name: Prefix Sum
slug: prefix-sum
difficulty_level: 2
pattern_type: technique
display_order: 5
description: >
Precompute cumulative sums to answer range sum queries in O(1) time. This

View File

@@ -1,6 +1,8 @@
name: Sliding Window
slug: sliding-window
difficulty_level: 2
pattern_type: algorithm
display_order: 2
description: >
Maintain a window of elements that slides through the data, tracking a
@@ -192,3 +194,129 @@ related_patterns:
prerequisite_patterns:
- two-pointers
visualization_examples:
- id: max-sum-subarray
title: Maximum Sum Subarray of Size K
input:
nums: [2, 1, 5, 1, 3, 2]
k: 3
code: |
def max_sum_subarray(nums, k):
window_sum = sum(nums[:k])
max_sum = window_sum
for i in range(k, len(nums)):
window_sum += nums[i] - nums[i - k]
max_sum = max(max_sum, window_sum)
return max_sum
steps:
- id: step-1
description: "Initialize window with first k=3 elements: [2, 1, 5]. Sum = 8"
structures:
nums:
type: array
values:
- { value: 2, state: active, annotations: ["window"] }
- { value: 1, state: active, annotations: ["window"] }
- { value: 5, state: active, annotations: ["window"] }
- { value: 1, state: default }
- { value: 3, state: default }
- { value: 2, state: default }
pointers:
left: 0
right: 2
variables:
window_sum: 8
max_sum: 8
codeHighlight:
startLine: 2
endLine: 3
- id: step-2
description: "Slide window: remove 2, add 1. New window [1, 5, 1]. Sum = 7"
structures:
nums:
type: array
values:
- { value: 2, state: visited }
- { value: 1, state: active, annotations: ["window"] }
- { value: 5, state: active, annotations: ["window"] }
- { value: 1, state: active, annotations: ["window"] }
- { value: 3, state: default }
- { value: 2, state: default }
pointers:
left: 1
right: 3
variables:
window_sum: 7
max_sum: 8
i: 3
codeHighlight:
startLine: 5
endLine: 7
- id: step-3
description: "Slide window: remove 1, add 3. New window [5, 1, 3]. Sum = 9. New max!"
structures:
nums:
type: array
values:
- { value: 2, state: visited }
- { value: 1, state: visited }
- { value: 5, state: found, annotations: ["window"] }
- { value: 1, state: found, annotations: ["window"] }
- { value: 3, state: found, annotations: ["window"] }
- { value: 2, state: default }
pointers:
left: 2
right: 4
variables:
window_sum: 9
max_sum: 9
i: 4
codeHighlight:
startLine: 5
endLine: 7
- id: step-4
description: "Slide window: remove 5, add 2. New window [1, 3, 2]. Sum = 6"
structures:
nums:
type: array
values:
- { value: 2, state: visited }
- { value: 1, state: visited }
- { value: 5, state: visited }
- { value: 1, state: active, annotations: ["window"] }
- { value: 3, state: active, annotations: ["window"] }
- { value: 2, state: active, annotations: ["window"] }
pointers:
left: 3
right: 5
variables:
window_sum: 6
max_sum: 9
i: 5
codeHighlight:
startLine: 5
endLine: 7
- id: step-5
description: "Loop complete. Maximum sum found is 9 (window [5, 1, 3])"
structures:
nums:
type: array
values:
- { value: 2, state: visited }
- { value: 1, state: visited }
- { value: 5, state: found }
- { value: 1, state: found }
- { value: 3, state: found }
- { value: 2, state: visited }
variables:
max_sum: 9
codeHighlight:
startLine: 9
endLine: 9

View File

@@ -0,0 +1,104 @@
name: Topological Sort
slug: topological-sort
difficulty_level: 3
pattern_type: algorithm
display_order: 22
description: >
Order vertices in a directed acyclic graph (DAG) such that for every
edge u -> v, vertex u comes before v in the ordering.
when_to_use: |
- Course prerequisites / task dependencies
- Build order / compilation order
- Detecting cycles in directed graphs
- Any problem with "do A before B" constraints
metaphor: |
Imagine getting dressed: you must put on underwear before pants, socks
before shoes. Topological sort finds a valid order that respects all
"must come before" rules. If there's a cycle (shirt requires jacket,
jacket requires shirt), no valid order exists.
core_concept: |
Two main approaches:
**Kahn's Algorithm (BFS):** Start with nodes having no incoming edges
(in-degree 0). Process them, remove their edges, repeat. If all nodes
processed, valid order exists.
**DFS-based:** Do DFS, add nodes to result when backtracking (post-order).
Reverse at end. Cycle exists if we revisit a node in current path.
code_template: |
from collections import deque
def topological_sort_bfs(n: int, edges: list[tuple[int, int]]) -> list[int]:
"""Kahn's algorithm - returns empty list if cycle exists."""
# Build adjacency list and in-degree count
graph = [[] for _ in range(n)]
in_degree = [0] * n
for u, v in edges: # u -> v (u must come before v)
graph[u].append(v)
in_degree[v] += 1
# Start with nodes having no prerequisites
queue = deque([i for i in range(n) if in_degree[i] == 0])
result = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
# If we processed all nodes, valid topological order exists
return result if len(result) == n else []
recognition_signals:
- "prerequisites"
- "dependencies"
- "ordering tasks"
- "course schedule"
- "build order"
- "detect cycle in directed graph"
- "do X before Y"
common_mistakes:
- title: Forgetting Cycle Detection
description: |
Assuming input is always a valid DAG. Must check if all nodes were
processed (BFS) or if back-edge exists (DFS).
fix: |
BFS: Check `len(result) == n`. DFS: Track "visiting" state separately
from "visited."
- title: Wrong Edge Direction
description: |
Confusing "A depends on B" vs "A must come before B." These are
opposite edge directions.
fix: |
Clarify: If A depends on B, edge is B -> A (B comes before A).
variations:
- name: Course Schedule (cycle detection)
description: Return true/false if valid ordering exists
example: "course-schedule"
- name: Course Schedule II (find order)
description: Return the actual ordering
example: "course-schedule-ii"
- name: Alien Dictionary
description: Infer ordering from sorted alien words
example: "alien-dictionary"
related_patterns:
- bfs
- dfs
prerequisite_patterns:
- bfs
- dfs

View File

@@ -1,6 +1,8 @@
name: Binary Tree Traversal
slug: tree-traversal
difficulty_level: 2
pattern_type: algorithm
display_order: 8
description: >
Visit all nodes in a binary tree in specific orders: preorder (root-left-right),

View File

@@ -1,6 +1,8 @@
name: Trie
slug: trie
difficulty_level: 3
pattern_type: data_structure
display_order: 17
description: >
A tree-like data structure for efficient string prefix operations. Each node

View File

@@ -1,6 +1,8 @@
name: Two Pointers
slug: two-pointers
difficulty_level: 2
pattern_type: algorithm
display_order: 1
description: >
Use two pointers to traverse data from different positions, often moving

View File

@@ -1,6 +1,8 @@
name: Union Find
slug: union-find
difficulty_level: 3
pattern_type: data_structure
display_order: 18
description: >
Track disjoint sets with efficient union and find operations. Union-Find