mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-17 15:36:26 +00:00
feat(islands): add StudyDeck component and markdown decks for algorithms
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
// 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: `<StudyDeck deck="two-pointers" />`.
|
||||||
|
*/
|
||||||
|
export default function StudyDeck({ deck }: { deck: DeckName }) {
|
||||||
|
const markdown = decks[deck];
|
||||||
|
if (!markdown) {
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
Unknown study deck "{deck}". Available: {Object.keys(decks).join(", ")}.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={{ aspectRatio: "16 / 9", width: "100%", margin: "1.5rem 0" }}>
|
||||||
|
<Deck
|
||||||
|
style={{ width: "100%", height: "100%", borderRadius: "0.5rem", overflow: "hidden" }}
|
||||||
|
config={{
|
||||||
|
embedded: true,
|
||||||
|
hash: false,
|
||||||
|
// Only capture arrow keys while the deck has focus, so it
|
||||||
|
// doesn't hijack page scrolling.
|
||||||
|
keyboardCondition: "focused",
|
||||||
|
slideNumber: "c/t",
|
||||||
|
controlsTutorial: false,
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
}}
|
||||||
|
plugins={[RevealHighlight]}
|
||||||
|
>
|
||||||
|
<Markdown options={{ animateLists: true }}>{markdown}</Markdown>
|
||||||
|
</Deck>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
## 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?
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
## 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
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
// Vite `?raw` imports resolve to the file's text content.
|
||||||
|
declare module "*.md?raw" {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
## 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
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
## 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
|
||||||
Reference in New Issue
Block a user