From 5a819c7016c8433b6e3e234e65f8ec17872a1080 Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Wed, 19 Aug 2026 22:08:40 -0400 Subject: [PATCH] refactor(islands): remove study deck components and related markdown decks --- islands/StudyDeck.tsx | 46 ----------------- islands/decks/arrays-and-strings.md | 56 --------------------- islands/decks/binary-search.md | 63 ----------------------- islands/decks/hashing.md | 72 --------------------------- islands/decks/index.ts | 26 ---------- islands/decks/prefix-sum.md | 71 -------------------------- islands/decks/raw.d.ts | 5 -- islands/decks/sliding-window.md | 68 ------------------------- islands/decks/two-pointers.md | 77 ----------------------------- 9 files changed, 484 deletions(-) delete mode 100644 islands/StudyDeck.tsx delete mode 100644 islands/decks/arrays-and-strings.md delete mode 100644 islands/decks/binary-search.md delete mode 100644 islands/decks/hashing.md delete mode 100644 islands/decks/index.ts delete mode 100644 islands/decks/prefix-sum.md delete mode 100644 islands/decks/raw.d.ts delete mode 100644 islands/decks/sliding-window.md delete mode 100644 islands/decks/two-pointers.md diff --git a/islands/StudyDeck.tsx b/islands/StudyDeck.tsx deleted file mode 100644 index c3068f9..0000000 --- a/islands/StudyDeck.tsx +++ /dev/null @@ -1,46 +0,0 @@ -// reveal.js touches window/document at init — render on the client only. -export const client = "only"; - -import { Deck, Markdown } from "@revealjs/react"; -import RevealHighlight from "reveal.js/plugin/highlight"; -import "reveal.js/reveal.css"; -import "reveal.js/theme/black.css"; -import "reveal.js/plugin/highlight/monokai.css"; - -import { decks, type DeckName } from "./decks"; - -/** - * An embedded reveal.js slide deck for drilling a DS/algorithms topic. - * Usage in any MDX page: ``. - */ -export default function StudyDeck({ deck }: { deck: DeckName }) { - const markdown = decks[deck]; - if (!markdown) { - return ( -

- Unknown study deck "{deck}". Available: {Object.keys(decks).join(", ")}. -

- ); - } - return ( -
- - {markdown} - -
- ); -} diff --git a/islands/decks/arrays-and-strings.md b/islands/decks/arrays-and-strings.md deleted file mode 100644 index a4184a7..0000000 --- a/islands/decks/arrays-and-strings.md +++ /dev/null @@ -1,56 +0,0 @@ -## Arrays & Strings - -Ordered collections. Arrays are **mutable**, strings are **immutable** — every "edit" to a string builds a new one. - ---- - -## Cost model - -| Operation | Array | String | -| --- | --- | --- | -| Append / pop at end | O(1)* | O(n) | -| Insert / delete in middle | O(n) | O(n) | -| Random access | O(1) | O(1) | -| Membership check | O(n) | O(n) | - -*amortized — occasional resize, constant on average. - ---- - -## Recognition - -- Contiguous data + index arithmetic → array patterns apply -- Building a string piece by piece → collect parts, join once -- "In-place" requirement → think swap / overwrite, not rebuild - ---- - -## String building - -```js -// O(n^2): each += copies the whole string -let s = ""; -for (const ch of parts) s += ch; - -// O(n): buffer then join -const buf = []; -for (const ch of parts) buf.push(ch); -const out = buf.join(""); -``` - ---- - -## Recall - -- Why is append amortized O(1) and not plain O(1)? -- What does immutability cost when you "modify" one character? -- Which patterns sit on top of arrays? (two pointers, sliding window, prefix sum) - ---- - -## Drill - -- 1480 Running Sum of 1d Array -- 217 Contains Duplicate -- 189 Rotate Array -- 977 Squares of a Sorted Array diff --git a/islands/decks/binary-search.md b/islands/decks/binary-search.md deleted file mode 100644 index 22d0d81..0000000 --- a/islands/decks/binary-search.md +++ /dev/null @@ -1,63 +0,0 @@ -## Binary Search - -Halve a **monotonic** search space each step → O(log n). - ---- - -## Recognition - -- Sorted input + "find / insert position" -- "Minimize the maximum" / "first value where condition flips" -- Answer itself is numeric and checkable → binary search **on the answer** - ---- - -## Template - -```js -function search(nums, target) { - let lo = 0; - let hi = nums.length - 1; - while (lo <= hi) { - const mid = lo + ((hi - lo) >> 1); - if (nums[mid] === target) return mid; - if (nums[mid] < target) lo = mid + 1; - else hi = mid - 1; - } - return -1; // lo is the insert position -} -``` - ---- - -## First-true boundary - -```js -// smallest index where ok(i) is true; ok is false...false true...true -let lo = 0; -let hi = n; // exclusive; n means "never true" -while (lo < hi) { - const mid = lo + ((hi - lo) >> 1); - if (ok(mid)) hi = mid; - else lo = mid + 1; -} -return lo; -``` - -Most "hard" binary searches are this shape in disguise. - ---- - -## Pitfalls - -- Off-by-one: pick `lo <= hi` **or** `lo < hi` and stay consistent -- `mid = lo + ((hi - lo) >> 1)` avoids overflow and floats -- No progress → infinite loop: every branch must shrink the range - ---- - -## Recall - -- What property must the search space have? (not "sorted" — *monotonic*) -- Where does `lo` land when the target is absent? -- When do you search the answer space instead of the array? diff --git a/islands/decks/hashing.md b/islands/decks/hashing.md deleted file mode 100644 index 92f4c68..0000000 --- a/islands/decks/hashing.md +++ /dev/null @@ -1,72 +0,0 @@ -## Hashing - -Trade O(n) memory for **O(1) average lookup**. The workhorse behind "have I seen this before?". - ---- - -## Recognition - -- "Does X exist?" / "How many times?" → `Set` / `Map` -- Pair matching (complement lookup) → map value → index -- Grouping by a derived key → map key → bucket -- Counting characters → frequency map - ---- - -## Template — complement lookup (LC 1) - -```js -function twoSum(nums, target) { - const seen = new Map(); // value -> index - for (let i = 0; i < nums.length; i++) { - const need = target - nums[i]; - if (seen.has(need)) return [seen.get(need), i]; - seen.set(nums[i], i); - } -} -``` - -One pass: check for the partner **before** inserting yourself. - ---- - -## Template — grouping (LC 49) - -```js -function groupAnagrams(strs) { - const groups = new Map(); - for (const s of strs) { - const key = [...s].sort().join(""); - if (!groups.has(key)) groups.set(key, []); - groups.get(key).push(s); - } - return [...groups.values()]; -} -``` - -The whole trick is **choosing the canonical key**. - ---- - -## Pitfalls - -- Frequency compare needs both directions (surplus *and* deficit) -- Object keys coerce to strings — use `Map` for numbers -- Hashing is O(1) *average*; keys must be cheap to compute - ---- - -## Recall - -- Two Sum: why insert after the lookup, not before? -- Group Anagrams: name two valid canonical keys -- LC 128 Longest Consecutive: why check `!set.has(x - 1)` first? - ---- - -## Drill - -- 217 Contains Duplicate · 242 Valid Anagram -- 1 Two Sum · 49 Group Anagrams -- 347 Top K Frequent · 383 Ransom Note -- 205 Isomorphic Strings · 128 Longest Consecutive · 290 Word Pattern diff --git a/islands/decks/index.ts b/islands/decks/index.ts deleted file mode 100644 index fecbee6..0000000 --- a/islands/decks/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Markdown study decks rendered by the StudyDeck island (reveal.js). - * - * Each deck is a plain markdown file in this directory — edit the `.md` - * file to change its slides. Slides split on `---` lines; lists animate - * in one item at a time (see `animateLists` in StudyDeck.tsx). Problem - * numbers reference the curriculum in README.md and the solutions under - * `work/`. - */ -import arraysAndStrings from "./arrays-and-strings.md?raw"; -import binarySearch from "./binary-search.md?raw"; -import hashing from "./hashing.md?raw"; -import prefixSum from "./prefix-sum.md?raw"; -import slidingWindow from "./sliding-window.md?raw"; -import twoPointers from "./two-pointers.md?raw"; - -export const decks = { - "arrays-and-strings": arraysAndStrings, - "binary-search": binarySearch, - hashing, - "prefix-sum": prefixSum, - "sliding-window": slidingWindow, - "two-pointers": twoPointers, -} as const; - -export type DeckName = keyof typeof decks; diff --git a/islands/decks/prefix-sum.md b/islands/decks/prefix-sum.md deleted file mode 100644 index e0ba462..0000000 --- a/islands/decks/prefix-sum.md +++ /dev/null @@ -1,71 +0,0 @@ -## Prefix Sum - -Precompute cumulative totals once → any range sum in **O(1)**. - ---- - -## Recognition - -- Many range-sum queries over a static array -- "Count subarrays with sum k" (negatives allowed — window won't work) -- "Product/sum of everything except me" → prefix from both directions - ---- - -## Template — range query (LC 303) - -```js -// prefix[i] = sum of nums[0..i-1], prefix[0] = 0 -const prefix = [0]; -for (const x of nums) prefix.push(prefix[prefix.length - 1] + x); - -// sum of nums[l..r] inclusive: -const rangeSum = (l, r) => prefix[r + 1] - prefix[l]; -``` - -The leading 0 removes every edge case at `l = 0`. - ---- - -## Template — prefix + hashmap (LC 560) - -```js -function subarraySum(nums, k) { - const count = new Map([[0, 1]]); // empty prefix - let sum = 0; - let total = 0; - for (const x of nums) { - sum += x; - total += count.get(sum - k) ?? 0; // earlier prefix to cut off - count.set(sum, (count.get(sum) ?? 0) + 1); - } - return total; -} -``` - -sum(l..r) = prefix[r] − prefix[l−1] — so look up `sum - k`. - ---- - -## Pitfalls - -- Forgetting the seed `(0, 1)` misses subarrays starting at index 0 -- Count **before** inserting the current prefix (a subarray has length ≥ 1) -- Sliding window fails here when values can be negative - ---- - -## Recall - -- Why does LC 560 need a hashmap instead of a window? -- LC 238 Product Except Self: how do prefix and suffix passes combine? -- LC 525 Contiguous Array: what do you turn 0s into, and why? - ---- - -## Drill - -- 303 Range Sum Query · 1480 Running Sum -- 238 Product of Array Except Self -- 560 Subarray Sum Equals K -- 525 Contiguous Array · 974 Subarray Sums Divisible by K diff --git a/islands/decks/raw.d.ts b/islands/decks/raw.d.ts deleted file mode 100644 index 1fa5881..0000000 --- a/islands/decks/raw.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -// Vite `?raw` imports resolve to the file's text content. -declare module "*.md?raw" { - const content: string; - export default content; -} diff --git a/islands/decks/sliding-window.md b/islands/decks/sliding-window.md deleted file mode 100644 index 669748b..0000000 --- a/islands/decks/sliding-window.md +++ /dev/null @@ -1,68 +0,0 @@ -## Sliding Window - -Two pointers **plus incremental state** between them. Never recompute the window from scratch. - ---- - -## Recognition - -- "Longest / shortest **contiguous** subarray or substring such that…" -- Constraint is monotone: growing the window can only make it worse -- Fixed window size k → same idea, both ends move together - ---- - -## Template — variable window (LC 3) - -```js -function lengthOfLongestSubstring(s) { - const seen = new Set(); - let best = 0; - let l = 0; - for (let r = 0; r < s.length; r++) { - while (seen.has(s[r])) { // shrink until valid - seen.delete(s[l]); - l++; - } - seen.add(s[r]); - best = Math.max(best, r - l + 1); - } - return best; -} -``` - -Grow with `r`, shrink with `l` only while invalid. - ---- - -## Why it's O(n) - -- `r` moves n times, `l` moves at most n times -- Each element enters and leaves the window **once** -- The inner `while` is amortized, not nested work - ---- - -## Pitfalls - -- State must update in O(1) on both add and remove -- "Shortest window" flips the loop: shrink while **valid**, record before breaking -- Doesn't apply when the constraint isn't monotone (mixed signs → prefix sum) - ---- - -## Recall - -- LC 121 Best Time to Buy/Sell: what is the "window state"? (min so far) -- LC 76 Minimum Window: what makes a window "valid"? -- When must you fall back to prefix sums instead? - ---- - -## Drill - -- 121 Best Time to Buy and Sell Stock -- 3 Longest Substring Without Repeating Characters -- 424 Longest Repeating Character Replacement -- 567 Permutation in String · 76 Minimum Window Substring -- 209 Minimum Size Subarray Sum diff --git a/islands/decks/two-pointers.md b/islands/decks/two-pointers.md deleted file mode 100644 index 7308a88..0000000 --- a/islands/decks/two-pointers.md +++ /dev/null @@ -1,77 +0,0 @@ -## Two Pointers - -Walk two indices instead of trying all pairs — **order lets you prune O(n²) → O(n)**. - ---- - -## Recognition - -- Sorted array + pair/target condition → converge from both ends -- Palindrome / symmetric check → ends inward -- In-place partition or dedupe → slow writer + fast reader -- Two sequences compared → one pointer each - ---- - -## Template — converging (LC 167) - -```js -function twoSumSorted(nums, target) { - let l = 0; - let r = nums.length - 1; - while (l < r) { - const sum = nums[l] + nums[r]; - if (sum === target) return [l + 1, r + 1]; - if (sum < target) l++; // need a bigger sum - else r--; // need a smaller sum - } -} -``` - -Each step discards every pair using the abandoned index — that's the proof. - ---- - -## Template — fast/slow writer (LC 977 idea) - -```js -function sortedSquares(nums) { - const out = new Array(nums.length); - let l = 0; - let r = nums.length - 1; - for (let i = nums.length - 1; i >= 0; i--) { - const a = nums[l] * nums[l]; - const b = nums[r] * nums[r]; - if (a > b) { out[i] = a; l++; } - else { out[i] = b; r--; } - } - return out; -} -``` - -Largest squares live at the **edges** — fill the output backwards. - ---- - -## Pitfalls - -- Moving the wrong pointer breaks the pruning argument -- Duplicates in 3Sum: skip repeats after each fixed element -- `l < r` vs `l <= r`: do the pointers meet or cross? - ---- - -## Recall - -- Container With Most Water: why move the *shorter* wall? -- 3Sum = sort + fix one + which pattern inside? -- Valid Palindrome: what do you do with non-alphanumerics? - ---- - -## Drill - -- 125 Valid Palindrome · 167 Two Sum II -- 15 3Sum · 11 Container With Most Water -- 42 Trapping Rain Water · 392 Is Subsequence -- 977 Squares of a Sorted Array · 80 Remove Duplicates II