Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0009075138 | ||
|
|
ca73d3235c | ||
|
|
97b62f95c9 | ||
|
|
1a440e55ce | ||
|
|
fd3b1952b1 | ||
|
|
82c26e2b6c | ||
|
|
f78ecb2bd4 | ||
|
|
b3d3c5fbc9 | ||
|
|
99a9248368 | ||
|
|
2a925f6a9a | ||
|
|
7d57faa958 |
No files matched your search
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
# Pre-commit: the docs pages under apps/docs/content are generated from work/1
|
||||
# by `bun run sync`, and deploy.yml publishes whatever main holds. Regenerate
|
||||
# them now and stage what sync changed, so a solution and its page land in the
|
||||
# same commit and a stale `## Solution` never reaches main.
|
||||
#
|
||||
# Sync reads the working tree, not the index, so the pages it writes only
|
||||
# match this commit when work/1 has nothing unstaged: an untracked or
|
||||
# half-staged solution would ship a page whose source is not in the commit.
|
||||
# In that case refuse and say what to stage.
|
||||
#
|
||||
# Only paths that became dirty during sync are staged. Anything already dirty
|
||||
# beforehand (hand-written `## Approach` prose in progress, a new index.mdx)
|
||||
# stays where the author left it.
|
||||
#
|
||||
# Bypass for a deliberate partial commit: git commit --no-verify
|
||||
set -eu
|
||||
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
|
||||
unstaged=$(git status --porcelain -- work/1 | grep -v '^[MADR] ' || true)
|
||||
if [ -n "$unstaged" ]; then
|
||||
printf 'pre-commit: work/1 has unstaged changes; stage or stash them so the generated pages match this commit:\n%s\n' "$unstaged" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
before=$(mktemp)
|
||||
trap 'rm -f "$before"' EXIT
|
||||
git status --porcelain -- apps/docs/content | sort > "$before"
|
||||
report=$(bun run --silent sync)
|
||||
generated=$(git status --porcelain -- apps/docs/content | sort | comm -13 "$before" - | cut -c4-)
|
||||
if [ -n "$generated" ]; then
|
||||
printf '%s\n' "$report" | grep -v 'skipped (in sync)' >&2
|
||||
printf '%s\n' "$generated" | xargs git add --
|
||||
fi
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Pre-push: the docs pages under apps/docs/content are generated from work/1
|
||||
# by `bun run sync`, and deploy.yml publishes whatever main holds. Regenerate
|
||||
# them now and refuse the push if the result differs from the tree, so a
|
||||
# solution never lands without its page (or with a stale `## Solution`).
|
||||
#
|
||||
# Bypass for a deliberate partial push: git push --no-verify
|
||||
set -eu
|
||||
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
report=$(bun run --silent sync)
|
||||
|
||||
changed=$(git status --porcelain -- apps/docs/content)
|
||||
if [ -n "$changed" ]; then
|
||||
printf '%s\n' "$report" | grep -v 'skipped (in sync)' >&2
|
||||
printf '\npre-push: apps/docs/content is out of sync with work/1 — commit the regenerated pages:\n%s\n' "$changed" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -44,10 +44,10 @@ flowchart LR
|
||||
|---|---|
|
||||
| `work/<1\|3\|7>/<Difficulty>/<Category>/<num>.<slug>.{js,py}` | Solutions, e.g. `work/1/Easy/Array/1.two-sum.py`. The top directory is the spaced-repetition bucket: `1` = first solve, `3`/`7` = the blind re-solves of that problem 3 and 7 days later. Machine-parsed header comment (title / `Difficulty:` / URL / `─` rule / statement) — preserve its exact shape |
|
||||
| `apps/cli/` | Flat Bun TS workspace: automation entries (shebang + top-level await) and libraries (no shebang, side-effect-free on import). Root `bun run` scripts delegate here |
|
||||
| `apps/docs/` | Blume site (`blume.config.ts`, `content/`, `islands/`, `public/`). `content/<topic>/<num>-<slug>.mdx` plus one `meta.ts` per topic folder, all generated by sync: the folder is the README campaign topic slug (`tree-dfs/`, `heap-priority-queue/`) and a real route segment (`/tree-dfs/invert-binary-tree`), the `meta.ts` carries the exact label and topic number so the sidebar reads in curriculum order, and the page badge is the `work/` data-structure category. A hand-written `<topic>/index.mdx` (`hidden: true`) is that topic's landing page: Blume ≥ 1.7.3 links the sidebar group row to it (`sliding-window/index.mdx` → `/sliding-window`) and the hidden flag drops the duplicate row |
|
||||
| `apps/docs/` | Blume site (`blume.config.ts`, `content/`, `components/` + `components.ts`, `islands/`, `public/`). `content/<topic>/<num>-<slug>.mdx` plus one `meta.ts` per topic folder, all generated by sync: the folder is the README campaign topic slug (`tree-dfs/`, `heap-priority-queue/`) and a real route segment (`/tree-dfs/invert-binary-tree`), the `meta.ts` carries the exact label and topic number so the sidebar reads in curriculum order, and the page badge is the `work/` data-structure category. A hand-written `<topic>/index.mdx` (`hidden: true`) is that topic's landing page: Blume ≥ 1.7.3 links the sidebar group row to it (`sliding-window/index.mdx` → `/sliding-window`) and the hidden flag drops the duplicate row. Two kinds of custom MDX component, both import-free on every page: `islands/<Name>.tsx` is hydrated React (auto-discovered; `ProgressDashboard`, `DependencySpine` — the README topic map as data, zoom/pan, nodes link to whatever route Blume's `/api/docs/navigation.json` reports for the topic folder, inert until a topic has pages), `components/<Name>.astro` registered in `components.ts` is static markup plus a custom element for behaviour, the way Blume's own `Frame` is built (`Carousel`/`CarouselImage`: `src` takes a URL, `/file.png`, or the page-relative `../../public/file.png`) |
|
||||
| `apps/api/` | Cloudflare Worker workspace: `src/` modules, `data/schedule.json` (the plan: `date → { topics | problems | gate | sweep }`, human-edited, bundled at deploy), `migrations/` (append-only; `0002` is the +3/+7 ladder rebuild) |
|
||||
| `.github/workflows/` | deploy (docs → Pages, Worker → Cloudflare, on every main push), close-solved (issues + `/admin/solved` + Project `Set`/`Difficulty`, needs `SRS_ADMIN_KEY` and `PROJECT_PAT`), close-topics, sync-d1 (issue edits → `/admin/reconcile`), spaced-repetition (daily 06:00 ET cron → today's review issue; `GITHUB_TOKEN` only, no secrets) |
|
||||
| `.githooks/` | Tracked git hooks; `bun install` points `core.hooksPath` here (root `prepare` script, no husky). `pre-push` runs `bun run sync` and refuses the push while `apps/docs/content` differs from the tree, so a solution never reaches `main` (and `deploy.yml`) without its page. `git push --no-verify` for a deliberate partial push |
|
||||
| `.githooks/` | Tracked git hooks; `bun install` points `core.hooksPath` here (root `prepare` script, no husky). `pre-commit` runs `bun run sync` and stages only the pages sync changed, so a solution and its page land in one commit; it refuses to run while `work/1` has anything unstaged (sync reads the tree, so the page would not match the commit), and leaves pre-existing dirt under `apps/docs/content` (in-progress `## Approach` prose) unstaged. `git commit --no-verify` for a deliberate partial commit |
|
||||
|
||||
## Development Commands
|
||||
|
||||
@@ -106,7 +106,7 @@ curl -X POST -H "Authorization: Bearer $LINK_KEY" \
|
||||
- `apps/api/src/index.ts` — router + cron dispatch; `apps/api/wrangler.jsonc` — bindings (`DB`, `EMAIL`), crons, vars; secrets `GH_PAT`/`WEBHOOK_SECRET`/`LINK_KEY` via `wrangler secret put`.
|
||||
- `apps/api/src/digest.ts` / `apps/api/src/email.tsx` — the daily digest, split data/presentation. `digest.ts` reads D1 into a `DigestData`; `email.tsx` owns every colour and every sentence, and renders both the HTML and (via its own `plainDigest`, not React Email's `plainText` mode, which flattens the tables) the text alternative. The retrieval rules hold by construction: `DigestRow` has no title and no issue field, so a review or drill line *cannot* leak the topic or a solution link. Subject is `(Day N/56) LeetCode Daily Digest`.
|
||||
- `apps/api/src/png.ts` — hand-rolled PNG encoder (RGB8, one IDAT, zlib via `CompressionStream("deflate")`, CRC32, 5×7 bitmap font). It exists because every major email client refuses remote SVG, so `/chart/heatmap.png` rasters the heatmap for the digest while `/chart/heatmap.svg` keeps serving the README byte-for-byte. Both come from one `heatmapCells()` so the two pictures cannot drift.
|
||||
- `apps/cli/sync.ts` — work→docs contract: on existing pages only the `<Badge>` line and the `## Solution` section are script-owned, the latter bounded by `solutionEnd()` at the next unfenced top-level heading; human prose before it and any sections after it are never touched. Never hand-write solution pages or edit inside `## Solution`. Two classifications, two homes: the sidebar group is the README topic (`readmeTopics()`, first listing wins for a problem under two topics; a warm-up outside the plan groups under its category, unordered, after the topics) and the badge is the `work/1/<Difficulty>/<Category>/` category — topic folders and their `meta.ts` are rewritten every run, a page is `rename`d when its topic changes, and a folder every page has left is deleted unless it still holds a hand-written `index.mdx` (sync never writes, moves, or deletes one). Never use `(group)` folders here: they add no route segment, so a topic `index.mdx` inside one collides with the root `/`. The two hand-owned sections around the solution have their own owners: `## Approach` (a Mermaid flowchart tracing the page's code, placed *before* `## Solution` so a re-sync keeps it) is written per the `problem-approach` skill (`/problem-approach` fills every page missing one); `## Explanation` (the NeetCode `<YouTube>` embed, after `## Solution`) is appended by `bun run videos --write` (`apps/cli/videos.ts`).
|
||||
- `apps/cli/sync.ts` — work→docs contract: on existing pages only the `<Badge>` line and the `## Solution` section are script-owned, the latter bounded by `solutionEnd()` at the next unfenced top-level heading; human prose before it and any sections after it are never touched. Never hand-write solution pages or edit inside `## Solution`. Two classifications, two homes: the sidebar group is the README topic (`readmeTopics()`, first listing wins for a problem under two topics; a warm-up outside the plan groups under its category at `order: 0`, ahead of topic 1) and the badge is the `work/1/<Difficulty>/<Category>/` category — topic folders and their `meta.ts` are rewritten every run, a page is `rename`d when its topic changes, and a folder every page has left is deleted unless it still holds a hand-written `index.mdx` (sync never writes, moves, or deletes one). Never use `(group)` folders here: they add no route segment, so a topic `index.mdx` inside one collides with the root `/`. The two hand-owned sections around the solution have their own owners: `## Approach` (a Mermaid flowchart tracing the page's code, placed *before* `## Solution` so a re-sync keeps it) is written per the `problem-approach` skill (`/problem-approach` fills every page missing one); `## Explanation` (the NeetCode `<YouTube>` embed, after `## Solution`) is appended by `bun run videos --write` (`apps/cli/videos.ts`).
|
||||
- `apps/cli/work.ts` — the `work/` layout: bucket paths, `isSolution()`/`solvedInBucket()` (the stub test, shared with `close-solved.ts`), `firstSolved()` (one `git log`, ET dates, keyed by LC number so past layout moves don't matter), and `reviewQueues()` (what each window still owes). Everything that needs to know where a solution file lives, or when it was solved, goes through here.
|
||||
- `apps/api/src/stats.ts` / `apps/cli/stats.ts` / `apps/cli/tui.ts` / `apps/cli/api.ts` — the read side. `buildStats()` is the ONE aggregation: phases, ladder, difficulty, per-topic temperature, gates, the 14-day due queue, and `heat` (every campaign day, zeros and future days included — the docs page windows the tail off it rather than asking a second question). Two readers consume it, the docs island and `bun run stats`, so its shape is both their contract: change all three together. `tui.ts` is hand-rolled ANSI (bars, sparklines, heat cells, alternate screen) on charts.ts's palette, and every chart differs by GLYPH as well as by colour so a piped or `NO_COLOR` dashboard still reads. The TUI recomputes nothing: the only local fact it shows is the `work/` solution count, which is inventory, not schedule.
|
||||
- `README.md` live charts are Worker endpoints (`/chart/*.svg`, `/badge/gate.svg`, 5-min Camo cache); the docs `/progress` page fetches `/api/stats` client-side (`apps/docs/islands/ProgressDashboard.tsx`).
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
<p align="center">
|
||||
<a href="https://x.com/basedprad"><img alt="follow" src="https://shieldcn.dev/x/follow/basedprad.svg" /></a>
|
||||
<a href="https://leetcode.com/progress"><img alt="Custom badge" src="https://shieldcn.dev/badge/view-@prdlk.svg?logo=leetcode" /></a>
|
||||
<a href="https://lc.prad.nu"><img alt="badge" src="https://shieldcn.dev/badge/Read-Documentation.svg?logo=lu%3ABook" /></a>
|
||||
<a href="https://lc.prad.nu/progress"><img alt="gate pass rate" src="https://srs-api.prdlk.workers.dev/badge/gate.svg" /></a>
|
||||
<a href="https://prdlk.github.io/leetcode"><img alt="badge" src="https://shieldcn.dev/badge/Read-Documentation.svg?logo=lu%3ABook" /></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
||||
@@ -196,10 +196,13 @@ async function readmeTopics(): Promise<Map<number, Topic>> {
|
||||
return map;
|
||||
}
|
||||
|
||||
/** Blume folder meta: exact topic label (the folder slug loses `/` and case) and curriculum order. */
|
||||
/**
|
||||
* Blume folder meta: exact topic label (the folder slug loses `/` and case) and
|
||||
* curriculum order. An unplanned category group (warm-ups) has no README
|
||||
* number and sorts at 0, ahead of topic 1, so the sidebar opens on the basics.
|
||||
*/
|
||||
function groupMeta(topic: Topic): string {
|
||||
const order = topic.order === undefined ? "" : `, order: ${topic.order}`;
|
||||
return `import { defineMeta } from "blume";\n\nexport default defineMeta({ title: ${JSON.stringify(topic.name)}${order} });\n`;
|
||||
return `import { defineMeta } from "blume";\n\nexport default defineMeta({ title: ${JSON.stringify(topic.name)}, order: ${topic.order ?? 0} });\n`;
|
||||
}
|
||||
|
||||
// ── inline-code formatting heuristics ────────────────────────────
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Project MDX components, available on every page with no import. Static
|
||||
* `.astro` files ship no framework JS (a custom element carries any
|
||||
* behaviour); interactive React lives in `islands/` instead.
|
||||
*/
|
||||
import { defineComponents } from "blume";
|
||||
|
||||
export default defineComponents({
|
||||
mdx: {
|
||||
Carousel: "./components/Carousel.astro",
|
||||
CarouselImage: "./components/CarouselImage.astro",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
/**
|
||||
* A horizontal carousel of <CarouselImage> slides, built the way Blume's own
|
||||
* content components are: static markup, a custom element for the behaviour,
|
||||
* no framework. The track is a CSS scroll-snap strip (so it also swipes on
|
||||
* touch and the browser does the animation); prev/next sit outside it and
|
||||
* the play toggle sits below with a "n / N" counter.
|
||||
*
|
||||
* `time` is seconds per slide while playing (default 1.25); `autoplay` starts
|
||||
* playing on load. Play wraps around, pauses whenever the tab is hidden, and
|
||||
* never starts on its own for a reader who prefers reduced motion — the
|
||||
* button still works for them. Manual navigation (buttons, arrow keys, swipe)
|
||||
* does not stop playback; it only restarts the interval so a step is never
|
||||
* doubled.
|
||||
*/
|
||||
import Icon from "blume/components/Icon.astro";
|
||||
|
||||
interface Props {
|
||||
/** Seconds each slide stays while playing. */
|
||||
time?: number;
|
||||
autoplay?: boolean;
|
||||
}
|
||||
|
||||
const { time = 1.25, autoplay = false } = Astro.props;
|
||||
|
||||
const buttonClass =
|
||||
"inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-border bg-background text-muted-foreground transition-colors hover:border-foreground hover:text-foreground disabled:cursor-default disabled:opacity-40";
|
||||
---
|
||||
|
||||
<blume-carousel
|
||||
class="not-prose my-6 block"
|
||||
data-time={String(time)}
|
||||
data-autoplay={autoplay ? "" : undefined}
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<button class={buttonClass} data-prev type="button" aria-label="Previous slide">
|
||||
<Icon class="rtl:-scale-x-100" name="chevron-left" size={18} />
|
||||
</button>
|
||||
<div
|
||||
class="flex min-w-0 flex-1 snap-x snap-mandatory overflow-x-auto scroll-smooth rounded-blume [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
data-track
|
||||
tabindex="0"
|
||||
aria-live="polite"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
<button class={buttonClass} data-next type="button" aria-label="Next slide">
|
||||
<Icon class="rtl:-scale-x-100" name="chevron-right" size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center justify-center gap-3">
|
||||
<button class={buttonClass} data-play type="button" aria-label="Play" aria-pressed="false">
|
||||
<Icon class="[[aria-pressed=true]_&]:hidden" name="play" size={16} />
|
||||
<Icon class="[[aria-pressed=false]_&]:hidden" name="pause" size={16} />
|
||||
</button>
|
||||
<span class="text-muted-foreground text-xs tabular-nums leading-5" data-count></span>
|
||||
</div>
|
||||
</blume-carousel>
|
||||
|
||||
<script>
|
||||
class BlumeCarousel extends HTMLElement {
|
||||
#track!: HTMLElement;
|
||||
#play!: HTMLButtonElement;
|
||||
#count!: HTMLElement;
|
||||
#timer: number | null = null;
|
||||
#index = 0;
|
||||
|
||||
connectedCallback() {
|
||||
this.#track = this.querySelector("[data-track]")!;
|
||||
this.#play = this.querySelector("[data-play]")!;
|
||||
this.#count = this.querySelector("[data-count]")!;
|
||||
const prev = this.querySelector<HTMLButtonElement>("[data-prev]")!;
|
||||
const next = this.querySelector<HTMLButtonElement>("[data-next]")!;
|
||||
|
||||
const slides = this.#slides().length;
|
||||
if (slides < 2) {
|
||||
for (const b of [prev, next, this.#play]) b.disabled = true;
|
||||
}
|
||||
this.#render();
|
||||
|
||||
prev.addEventListener("click", () => this.#step(-1));
|
||||
next.addEventListener("click", () => this.#step(1));
|
||||
this.#play.addEventListener("click", () => (this.#timer === null ? this.#start() : this.#stop()));
|
||||
this.addEventListener("keydown", (event) => {
|
||||
if (event.key === "ArrowLeft") this.#step(-1);
|
||||
else if (event.key === "ArrowRight") this.#step(1);
|
||||
else return;
|
||||
event.preventDefault();
|
||||
});
|
||||
// Swipes and trackpad scrolls move the track without #step; follow them
|
||||
// once the scroll settles. Settle-only, not per event: a long smooth
|
||||
// scroll from #step passes over intermediate slides and must not be
|
||||
// read back as the reader's position mid-flight.
|
||||
let settle: number | undefined;
|
||||
this.#track.addEventListener(
|
||||
"scroll",
|
||||
() => {
|
||||
window.clearTimeout(settle);
|
||||
settle = window.setTimeout(() => this.#syncFromScroll(), 150);
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
document.addEventListener("visibilitychange", this.#onVisibility);
|
||||
|
||||
const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
if (this.hasAttribute("data-autoplay") && slides > 1 && !reduced) this.#start();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.#stop();
|
||||
document.removeEventListener("visibilitychange", this.#onVisibility);
|
||||
}
|
||||
|
||||
#slides(): HTMLElement[] {
|
||||
return [...this.#track.querySelectorAll<HTMLElement>(":scope > blume-carousel-image")];
|
||||
}
|
||||
|
||||
#step(delta: number) {
|
||||
const slides = this.#slides();
|
||||
this.#index = (this.#index + delta + slides.length) % slides.length;
|
||||
slides[this.#index]!.scrollIntoView({ block: "nearest", inline: "start", behavior: "smooth" });
|
||||
this.#render();
|
||||
if (this.#timer !== null) this.#start(); // restart the interval from this slide
|
||||
}
|
||||
|
||||
#syncFromScroll() {
|
||||
const at = Math.round(this.#track.scrollLeft / this.#track.clientWidth);
|
||||
if (at !== this.#index && Number.isFinite(at)) {
|
||||
this.#index = Math.min(Math.max(at, 0), this.#slides().length - 1);
|
||||
this.#render();
|
||||
}
|
||||
}
|
||||
|
||||
#start() {
|
||||
this.#stop();
|
||||
const ms = Number(this.dataset.time) * 1000;
|
||||
this.#timer = window.setInterval(() => this.#step(1), ms > 0 ? ms : 1250);
|
||||
this.#play.setAttribute("aria-pressed", "true");
|
||||
this.#play.setAttribute("aria-label", "Pause");
|
||||
}
|
||||
|
||||
#stop() {
|
||||
if (this.#timer !== null) window.clearInterval(this.#timer);
|
||||
this.#timer = null;
|
||||
this.#play.setAttribute("aria-pressed", "false");
|
||||
this.#play.setAttribute("aria-label", "Play");
|
||||
}
|
||||
|
||||
// A background tab keeps its timers but not its layout; resume on return.
|
||||
#wasPlaying = false;
|
||||
#onVisibility = () => {
|
||||
if (document.hidden) {
|
||||
this.#wasPlaying = this.#timer !== null;
|
||||
this.#stop();
|
||||
} else if (this.#wasPlaying) this.#start();
|
||||
};
|
||||
|
||||
#render() {
|
||||
const slides = this.#slides();
|
||||
this.#count.textContent = `${this.#index + 1} / ${slides.length}`;
|
||||
slides.forEach((s, i) => s.setAttribute("aria-hidden", i === this.#index ? "false" : "true"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get("blume-carousel")) {
|
||||
customElements.define("blume-carousel", BlumeCarousel);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
/**
|
||||
* One slide of a <Carousel>: Blume's own <Frame> around an <img>, so a slide
|
||||
* is pixel-identical to a framed image elsewhere on the site — `alt` doubles
|
||||
* as the frame's hint line and `caption` is Markdown, exactly as in Frame.
|
||||
*
|
||||
* `src` accepts what MDX authors already write for images: a URL, a
|
||||
* root-relative `/file.png` (served from public/, prefixed with the deploy
|
||||
* base), or the page-relative `../../public/file.png` form. A relative path
|
||||
* that does not pass through public/ has nothing to serve it, so it is a
|
||||
* build error rather than a silent 404.
|
||||
*/
|
||||
import Frame from "blume/components/content/Frame.astro";
|
||||
import { withBase } from "blume/components/islands/base-path.ts";
|
||||
|
||||
interface Props {
|
||||
src: string;
|
||||
alt?: string;
|
||||
caption?: string;
|
||||
}
|
||||
|
||||
const { src, alt, caption } = Astro.props;
|
||||
|
||||
const resolveSrc = (value: string): string => {
|
||||
if (URL.canParse(value)) return value;
|
||||
if (value.startsWith("/")) return withBase(value);
|
||||
const at = value.indexOf("public/");
|
||||
if (at === -1) {
|
||||
throw new Error(
|
||||
`<CarouselImage src="${value}">: a relative src must point into public/ (or use /file.png)`
|
||||
);
|
||||
}
|
||||
return withBase(`/${value.slice(at + "public/".length)}`);
|
||||
};
|
||||
---
|
||||
|
||||
<blume-carousel-image
|
||||
class="block w-full shrink-0 snap-center [&_blume-frame]:my-0"
|
||||
role="group"
|
||||
aria-roledescription="slide"
|
||||
>
|
||||
<Frame caption={caption} hint={alt}>
|
||||
<img alt={alt ?? ""} src={resolveSrc(src)} loading="lazy" draggable="false" />
|
||||
</Frame>
|
||||
</blume-carousel-image>
|
||||
@@ -1,3 +1,3 @@
|
||||
import { defineMeta } from "blume";
|
||||
|
||||
export default defineMeta({ title: "Array" });
|
||||
export default defineMeta({ title: "Array", order: 0 });
|
||||
@@ -3,7 +3,7 @@ title: Overview
|
||||
description: Live SRS campaign dashboard — phases, ladder, review queue, gates, and recent attempts, fetched from the SRS Worker.
|
||||
---
|
||||
|
||||

|
||||
<DependencySpine />
|
||||
|
||||
Live view of the 56-day campaign. Everything below is fetched client-side
|
||||
from the SRS Worker, so it is always current — no rebuild required.
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: '207. Course Schedule'
|
||||
description: There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai
|
||||
sidebar:
|
||||
label: 'Course Schedule'
|
||||
badge: 'Medium'
|
||||
---
|
||||
|
||||
<Badge variant="accent">Depth-First Search</Badge>
|
||||
|
||||
### Example 1:
|
||||
- Input: `numCourses = 2, prerequisites = [[1,0]]`
|
||||
- Output: `true`
|
||||
- Explanation: There are a total of `2` courses to take. To take course `1` you should have finished course `0`. So it is possible.
|
||||
|
||||
### Example 2:
|
||||
- Input: `numCourses = 2, prerequisites = [[1,0],[0,1]]`
|
||||
- Output: `false`
|
||||
- Explanation: There are a total of `2` courses to take. To take course `1` you should have finished course `0`, and to take course `0` you should also have finished course `1`. So it is impossible.
|
||||
|
||||
### Constraints:
|
||||
|
||||
- `1 <= numCourses <= 2000`
|
||||
- `0 <= prerequisites.length <= 5000`
|
||||
- `prerequisites[i].length == 2`
|
||||
- `0 <= ai, bi < numCourses`
|
||||
- All the pairs `prerequisites[i]` are unique.
|
||||
|
||||
## Solution
|
||||
|
||||
```py
|
||||
class Solution:
|
||||
def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool:
|
||||
indegree = [0] * numCourses
|
||||
adj = [[] for x in range(numCourses)]
|
||||
for prereq in prerequisites:
|
||||
adj[prereq[1]].append(prereq[0])
|
||||
indegree[prereq[0]] += 1
|
||||
|
||||
queue = []
|
||||
|
||||
for i in range(numCourses):
|
||||
if indegree[i] == 0:
|
||||
queue.append(i)
|
||||
|
||||
visited = 0
|
||||
while queue:
|
||||
node = queue.pop(0)
|
||||
visited += 1
|
||||
for neighbor in adj[node]:
|
||||
indegree[neighbor] -= 1
|
||||
if indegree[neighbor] == 0:
|
||||
queue.append(neighbor)
|
||||
|
||||
return numCourses == visited
|
||||
```
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: '210. Course Schedule II'
|
||||
description: There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai
|
||||
sidebar:
|
||||
label: 'Course Schedule II'
|
||||
badge: 'Medium'
|
||||
---
|
||||
|
||||
<Badge variant="accent">Depth-First Search</Badge>
|
||||
|
||||
### Example 1:
|
||||
- Input: `numCourses = 2, prerequisites = [[1,0]]`
|
||||
- Output: `[0,1]`
|
||||
- Explanation: There are a total of `2` courses to take. To take course `1` you should have finished course `0`. So the correct course order is `[0,1]`.
|
||||
|
||||
### Example 2:
|
||||
- Input: `numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]`
|
||||
- Output: `[0,2,1,3]`
|
||||
- Explanation: There are a total of `4` courses to take. To take course `3` you should have finished both courses `1` and `2`. Both courses `1` and `2` should be taken after you finished course `0`. So one correct course order is `[0,1,2,3]`. Another correct ordering is `[0,2,1,3]`.
|
||||
|
||||
### Example 3:
|
||||
- Input: `numCourses = 1, prerequisites = []`
|
||||
- Output: `[0]`
|
||||
|
||||
### Constraints:
|
||||
|
||||
- `1 <= numCourses <= 2000`
|
||||
- `0 <= prerequisites.length <= numCourses * (numCourses - 1)`
|
||||
- `prerequisites[i].length == 2`
|
||||
- `0 <= ai, bi < numCourses`
|
||||
- `ai != bi`
|
||||
- All the pairs [ai, bi] are distinct.
|
||||
|
||||
## Solution
|
||||
|
||||
```py
|
||||
class Solution:
|
||||
def findOrder(self, numCourses: int, prerequisites: list[list[int]]) -> list[int]:
|
||||
prereq = {c: [] for c in range(numCourses)}
|
||||
for crs, pre in prerequisites:
|
||||
prereq[crs].append(pre)
|
||||
|
||||
# course has 3 possible states:
|
||||
# visited -> crs has been added to output
|
||||
# visiting -> crs not added to output, but added to cycle
|
||||
# unvisited -> crs not added to output nor cycle
|
||||
output = []
|
||||
visit, cycle = set(), set()
|
||||
|
||||
def dfs(crs):
|
||||
if crs in cycle:
|
||||
return False
|
||||
if crs in visit:
|
||||
return True
|
||||
|
||||
cycle.add(crs)
|
||||
for pre in prereq[crs]:
|
||||
if dfs(pre) == False:
|
||||
return False
|
||||
|
||||
cycle.remove(crs)
|
||||
visit.add(crs)
|
||||
output.append(crs)
|
||||
return True
|
||||
|
||||
for c in range(numCourses):
|
||||
if dfs(c) == False:
|
||||
return []
|
||||
|
||||
return output
|
||||
```
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: Topological Sort
|
||||
description: 'Topological sort or topological ordering of a directed graph is an ordering of nodes such that every node appears in the ordering before all the nodes it points to. '
|
||||
hidden: true
|
||||
---
|
||||
|
||||
## Directed Graph
|
||||
|
||||
Before we get started on topological order, let's talk about directed graphs first. A graph is directed when its edges have directions (these edges are also called arcs). Suppose that v, w are vertices in a directed graph. Let's get familiar with a few concepts.
|
||||
|
||||
- **in-edge** - an edge pointing to or coming into the node
|
||||
- **out-edge** - an edge pointing away or going out from the node (we will refer those nodes on the other end of the out-edges of v to v's neighbors)
|
||||
- **in-degree** - the number of in-edges coming into the node
|
||||
- **out-degree** - the number of out-edges going out from the node
|
||||
- **dependency** - "v depends on w" / "v is dependent on w" means that there is a directed path from w to v where v, w are vertices in a directed graph. With these new terms under our belt, it will make more sense when we talk about directed graphs.
|
||||
|
||||
## What is a topological order?
|
||||
|
||||
Topological sort is not unique. For example, for a graph like this:
|
||||
|
||||

|
||||
|
||||
Both `[4, 5, 2]` and `[5, 4, 2]` are valid topological orders.
|
||||
|
||||

|
||||
|
||||
Task 3 is completely independent of task 2 and 4, and it can be anywhere in the order as long as it is before task 1 which depends on it. All of the following ordering are valid topological orderings.
|
||||
|
||||
`[4, 2, 3, 1], [4, 3, 2, 1], [3, 4, 2, 1]`
|
||||
|
||||
|
||||
:::warning[Graphs with Cycles Do Not Have Topological Ordering]
|
||||
It should be obvious that if a graph with a cycle does not have a topological ordering. In the above example, 2 has to come before 5 which has come before 4 which has to come before 2 which has to come before 5... which is impossible.
|
||||
:::
|
||||
|
||||
## Kahn's Algorithm
|
||||
|
||||
To obtain a topological order, we can use Kahn's algorithm which is very similar to Breadth First Search.
|
||||
|
||||
In order to understand why we must use this algorithm we first look at the problem it is trying to solve.
|
||||
|
||||
Given a directed graph does there exist a way to remove the nodes such that each time we remove a node we guarantee that no other nodes point to that particular node?
|
||||
|
||||
For this algorithm, we systematically remove one node at a time, each time removing a node such that no other nodes point to that node (in-degree is 0). If no such node exists, then there must be a cycle, and there is no way to order the nodes such that "every node appears in the ordering before all the nodes it points to" (no solution). After removing the current node, for each neighboring node the current node points to, we check whether any nodes point to this node. If there isn't any, we push this node into the queue. Notice it is important to keep track of the number of nodes pointing to a node (in-degree) in the question as we only push the node into the queue once all nodes the current node depended on have been removed.
|
||||
|
||||
Here is a graphic to demonstrate the idea.
|
||||
|
||||
<Carousel>
|
||||
<CarouselImage src='../../public/topo_kahn.001.png' />
|
||||
<CarouselImage src='../../public/topo_kahn.002.png' />
|
||||
<CarouselImage src='../../public/topo_kahn.003.png' />
|
||||
<CarouselImage src='../../public/topo_kahn.004.png' />
|
||||
<CarouselImage src='../../public/topo_kahn.005.png' />
|
||||
<CarouselImage src='../../public/topo_kahn.006.png' />
|
||||
<CarouselImage src='../../public/topo_kahn.007.png' />
|
||||
<CarouselImage src='../../public/topo_kahn.009.png' />
|
||||
<CarouselImage src='../../public/topo_kahn.010.png' />
|
||||
</Carousel>
|
||||
|
||||
## Example Code
|
||||
|
||||
:::tip
|
||||
Notice the topological sort algorithm is very similar to BFS. The main difference is that we only push nodes with 0 in-degree into the queue in topological sort whereas in BFS we push all the neighboring nodes into the queue.
|
||||
:::
|
||||
|
||||
```py
|
||||
from collections import deque
|
||||
|
||||
def find_indegree(graph):
|
||||
indegree = { node: 0 for node in graph } # dict
|
||||
for node in graph:
|
||||
for neighbor in graph[node]:
|
||||
indegree[neighbor] += 1
|
||||
return indegree
|
||||
|
||||
|
||||
def topo_sort(graph):
|
||||
res = []
|
||||
q = deque()
|
||||
indegree = find_indegree(graph)
|
||||
for node in indegree:
|
||||
if indegree[node] == 0:
|
||||
q.append(node)
|
||||
while len(q) > 0:
|
||||
node = q.popleft()
|
||||
res.append(node)
|
||||
for neighbor in graph[node]:
|
||||
indegree[neighbor] -= 1
|
||||
if indegree[neighbor] == 0:
|
||||
q.append(neighbor)
|
||||
return res if len(graph) == len(res) else None
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
import { defineMeta } from "blume";
|
||||
|
||||
export default defineMeta({ title: "Topological Sort", order: 18 });
|
||||
@@ -0,0 +1,377 @@
|
||||
/**
|
||||
* Dependency spine island — the "how the 24 topics build on each other" map
|
||||
* from the README, redrawn as data (nodes on a 7×6 grid, edges as SVG path
|
||||
* strings) so it is themed, zoomable, and clickable.
|
||||
*
|
||||
* - Wheel over the figure zooms about the cursor (trackpad pinch arrives as a
|
||||
* ctrl+wheel and behaves the same); dragging pans; the toolbar and +/-/0
|
||||
* keys do the same. Zooming OUT at the minimum scale is deliberately left
|
||||
* to the page, so a reader scrolling past the map is never trapped in it.
|
||||
* - A node is a link to its topic. Routes come from Blume's own navigation
|
||||
* endpoint at mount: the topic's landing page when the folder has an
|
||||
* `index.mdx`, else its first solution page. A topic with no pages yet is
|
||||
* drawn inert rather than linked to a 404, so the map stays honest as the
|
||||
* campaign fills in without a rebuild of this file.
|
||||
*
|
||||
* Invariants: the first frame is the figure at HOME_ZOOM, centred, with no
|
||||
* links (server and client render identically); the geometry is the README
|
||||
* SVG's, to the pixel, so `viewBox` maths below is in that 1472×648 space.
|
||||
*/
|
||||
import { useEffect, useRef, useState, type CSSProperties, type PointerEvent } from "react";
|
||||
import { withBase } from "blume/components/islands/base-path.ts";
|
||||
|
||||
export const client = "visible";
|
||||
|
||||
// ── figure data (README dependency-spine geometry) ───────────────
|
||||
|
||||
const W = 1472;
|
||||
const H = 648;
|
||||
const MAX_ZOOM = 6;
|
||||
const NODE_W = 144;
|
||||
const NODE_H = 48;
|
||||
const COL_X = [56, 248, 472, 664, 888, 1080, 1272] as const;
|
||||
const ROW_Y = [72, 144, 216, 288, 416, 488] as const;
|
||||
|
||||
type Kind = "entry" | "topic" | "focal";
|
||||
|
||||
interface Topic {
|
||||
n: number;
|
||||
name: string;
|
||||
sub?: string;
|
||||
/** Sync's group slug for the README topic name — the docs folder. */
|
||||
slug: string;
|
||||
kind: Kind;
|
||||
col: number;
|
||||
row: number;
|
||||
}
|
||||
|
||||
const TOPICS: Topic[] = [
|
||||
{ n: 5, name: "Binary Search", slug: "binary-search", kind: "entry", col: 0, row: 0 },
|
||||
{ n: 3, name: "Two Pointers", slug: "two-pointers", kind: "entry", col: 0, row: 1 },
|
||||
{ n: 4, name: "Sliding Window", slug: "sliding-window", kind: "topic", col: 1, row: 1 },
|
||||
{ n: 6, name: "Stack", slug: "stack", kind: "entry", col: 0, row: 2 },
|
||||
{ n: 7, name: "Monotonic Stack", slug: "monotonic-stack", kind: "topic", col: 1, row: 2 },
|
||||
{ n: 1, name: "Hash Lookup", slug: "hash-table", kind: "entry", col: 0, row: 3 },
|
||||
{ n: 2, name: "Prefix Sum", slug: "prefix-sum", kind: "topic", col: 1, row: 3 },
|
||||
{ n: 11, name: "Tree DFS", slug: "tree-dfs", kind: "focal", col: 2, row: 0 },
|
||||
{ n: 13, name: "Binary Search Tree", slug: "binary-search-tree", kind: "topic", col: 2, row: 1 },
|
||||
{ n: 14, name: "Heap", sub: "priority queue", slug: "heap-priority-queue", kind: "topic", col: 3, row: 1 },
|
||||
{ n: 15, name: "Trie", slug: "trie", kind: "topic", col: 3, row: 2 },
|
||||
{ n: 12, name: "Tree BFS", slug: "tree-bfs", kind: "entry", col: 2, row: 3 },
|
||||
{ n: 8, name: "Linked List", slug: "linked-list", kind: "entry", col: 2, row: 4 },
|
||||
{ n: 9, name: "Hybrid Structures", slug: "hybrid-structures", kind: "topic", col: 3, row: 4 },
|
||||
{ n: 10, name: "Matrix Index Math", slug: "matrix-index-math", kind: "topic", col: 2, row: 5 },
|
||||
{ n: 16, name: "Graph DFS", slug: "graph-dfs", kind: "topic", col: 4, row: 0 },
|
||||
{ n: 18, name: "Topological Sort", slug: "topological-sort", kind: "topic", col: 5, row: 0 },
|
||||
{ n: 19, name: "Union-Find", slug: "union-find", kind: "topic", col: 4, row: 1 },
|
||||
{ n: 20, name: "Backtracking", slug: "backtracking", kind: "topic", col: 5, row: 1 },
|
||||
{ n: 21, name: "1-D DP", sub: "dynamic programming", slug: "1-d-dynamic-programming", kind: "focal", col: 5, row: 2 },
|
||||
{ n: 22, name: "Multi-D / Grid DP", slug: "multi-d-grid-dp", kind: "topic", col: 6, row: 2 },
|
||||
{ n: 17, name: "Graph BFS", slug: "graph-bfs", kind: "topic", col: 4, row: 3 },
|
||||
{ n: 23, name: "Greedy", slug: "greedy", kind: "topic", col: 5, row: 3 },
|
||||
{ n: 24, name: "Intervals", slug: "intervals", kind: "topic", col: 6, row: 3 },
|
||||
];
|
||||
|
||||
/** "builds on" arrows, source → target, in the figure's coordinate space. */
|
||||
const EDGES: [string, string][] = [
|
||||
["3 → 4", "M200,168 H248"],
|
||||
["6 → 7", "M200,240 H248"],
|
||||
["1 → 2", "M200,312 H248"],
|
||||
["8 → 9", "M616,440 H664"],
|
||||
["16 → 18", "M1032,96 H1080"],
|
||||
["21 → 22", "M1224,240 H1272"],
|
||||
["23 → 24", "M1224,312 H1272"],
|
||||
["11 → 16", "M616,84 H888"],
|
||||
["12 → 17", "M616,312 H888"],
|
||||
["11 → 13", "M544,120 V144"],
|
||||
["16 → 19", "M960,120 V144"],
|
||||
["18 → 20", "M1152,120 V144"],
|
||||
["20 → 21", "M1152,192 V216"],
|
||||
["21 → 23", "M1152,264 V288"],
|
||||
["5 → 13", "M200,96 H416 Q424,96 424,104 V160 Q424,168 432,168 H472"],
|
||||
["11 → 14", "M616,96 H640 Q648,96 648,104 V160 Q648,168 656,168 H664"],
|
||||
["11 → 15", "M616,108 H624 Q632,108 632,116 V232 Q632,240 640,240 H664"],
|
||||
["4 → 10", "M392,184 H432 Q440,184 440,192 V504 Q440,512 448,512 H472"],
|
||||
["1 → 9", "M128,336 V560 Q128,568 136,568 H728 Q736,568 736,560 V464"],
|
||||
];
|
||||
|
||||
const ZONES: [string, number, number, number, number, number][] = [
|
||||
// label, x, y, w, h, label plate width
|
||||
["PHASE I — LINEAR STRUCTURES", 40, 40, 368, 312, 168],
|
||||
["PHASE III — HIERARCHICAL", 456, 40, 368, 312, 152],
|
||||
["PHASE II — NODAL & GRID", 456, 384, 368, 168, 144],
|
||||
["PHASES IV–V — RELATIONAL & DECISION-SPACE", 872, 40, 560, 312, 264],
|
||||
];
|
||||
|
||||
// ── theme ────────────────────────────────────────────────────────
|
||||
// Everything is a Blume token so the map follows the site's light/dark
|
||||
// scheme instead of the README's fixed black; tints go through color-mix.
|
||||
|
||||
const fg = "var(--blume-foreground)";
|
||||
const muted = "var(--blume-muted-foreground)";
|
||||
const accent = "var(--blume-accent)";
|
||||
const tint = (color: string, pct: number) => `color-mix(in oklab, ${color} ${pct}%, transparent)`;
|
||||
|
||||
const NODE_STYLE: Record<Kind, { fill: string; stroke: string; strokeWidth: number; tag: string }> = {
|
||||
entry: { fill: tint(muted, 10), stroke: tint(muted, 70), strokeWidth: 1, tag: muted },
|
||||
topic: { fill: tint(fg, 4), stroke: fg, strokeWidth: 1, tag: muted },
|
||||
focal: { fill: tint(accent, 12), stroke: accent, strokeWidth: 1.2, tag: accent },
|
||||
};
|
||||
|
||||
const mono: CSSProperties = { fontFamily: "var(--blume-font-mono, ui-monospace, monospace)" };
|
||||
const sans: CSSProperties = { fontFamily: "var(--blume-font-sans, system-ui, sans-serif)" };
|
||||
|
||||
const toolButton: CSSProperties = {
|
||||
width: "1.75rem",
|
||||
height: "1.75rem",
|
||||
border: "1px solid var(--blume-border)",
|
||||
borderRadius: "9999px",
|
||||
background: "var(--blume-background)",
|
||||
color: muted,
|
||||
font: "inherit",
|
||||
lineHeight: 1,
|
||||
cursor: "pointer",
|
||||
};
|
||||
|
||||
// ── navigation lookup ────────────────────────────────────────────
|
||||
|
||||
interface NavGroup {
|
||||
kind: "group";
|
||||
path: string;
|
||||
route?: string | null;
|
||||
children: unknown[];
|
||||
}
|
||||
|
||||
const isGroup = (v: unknown): v is NavGroup =>
|
||||
typeof v === "object" && v !== null && (v as NavGroup).kind === "group" &&
|
||||
typeof (v as NavGroup).path === "string" && Array.isArray((v as NavGroup).children);
|
||||
|
||||
/** The first page route under a sidebar entry, depth-first. */
|
||||
const firstRoute = (items: unknown[]): string | null => {
|
||||
for (const item of items) {
|
||||
if (typeof item !== "object" || item === null) continue;
|
||||
const v = item as { route?: unknown; children?: unknown };
|
||||
if (typeof v.route === "string") return v.route;
|
||||
if (Array.isArray(v.children)) {
|
||||
const inner = firstRoute(v.children);
|
||||
if (inner) return inner;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/** folder path (`/tree-dfs`) → the route a node should open. */
|
||||
const topicRoutes = (nav: unknown): Record<string, string> => {
|
||||
const out: Record<string, string> = {};
|
||||
const sidebar = (nav as { sidebar?: unknown })?.sidebar;
|
||||
if (!Array.isArray(sidebar)) return out;
|
||||
for (const entry of sidebar) {
|
||||
if (!isGroup(entry)) continue;
|
||||
const route = entry.route ?? firstRoute(entry.children);
|
||||
if (route) out[entry.path] = route;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
// ── component ────────────────────────────────────────────────────
|
||||
|
||||
interface ViewBox { x: number; y: number; w: number; h: number }
|
||||
/** The resting view — 2× on the centre so node labels read at the content width; 1× stays reachable by zooming out. */
|
||||
const HOME_ZOOM = 2;
|
||||
const HOME: ViewBox = { x: (W - W / HOME_ZOOM) / 2, y: (H - H / HOME_ZOOM) / 2, w: W / HOME_ZOOM, h: H / HOME_ZOOM };
|
||||
const clamp = (v: number, lo: number, hi: number) => Math.min(Math.max(v, lo), hi);
|
||||
|
||||
export default function DependencySpine() {
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
const [vb, setVbState] = useState<ViewBox>(HOME);
|
||||
// The native wheel listener and back-to-back events read the latest view
|
||||
// from the ref, which is written synchronously — state alone lags a render.
|
||||
const vbRef = useRef(vb);
|
||||
const setVb = (next: ViewBox) => {
|
||||
vbRef.current = next;
|
||||
setVbState(next);
|
||||
};
|
||||
const [routes, setRoutes] = useState<Record<string, string> | null>(null);
|
||||
const drag = useRef<{ x: number; y: number; moved: boolean } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let live = true;
|
||||
fetch(withBase("/api/docs/navigation.json"))
|
||||
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
|
||||
.then((nav) => live && setRoutes(topicRoutes(nav)))
|
||||
.catch(() => live && setRoutes({}));
|
||||
return () => { live = false; };
|
||||
}, []);
|
||||
|
||||
/** Zoom by `factor` (>1 in) about a client-space point; omitted = centre. */
|
||||
const zoomAt = (factor: number, clientX?: number, clientY?: number) => {
|
||||
const rect = svgRef.current?.getBoundingClientRect();
|
||||
const cur = vbRef.current;
|
||||
if (!rect) return;
|
||||
const px = clientX === undefined ? cur.x + cur.w / 2 : cur.x + ((clientX - rect.left) / rect.width) * cur.w;
|
||||
const py = clientY === undefined ? cur.y + cur.h / 2 : cur.y + ((clientY - rect.top) / rect.height) * cur.h;
|
||||
const w = clamp(cur.w / factor, W / MAX_ZOOM, W);
|
||||
const f = cur.w / w;
|
||||
const h = H * (w / W);
|
||||
setVb({ x: clamp(px - (px - cur.x) / f, 0, W - w), y: clamp(py - (py - cur.y) / f, 0, H - h), w, h });
|
||||
};
|
||||
|
||||
// React registers `wheel` as passive, so preventDefault has to go native.
|
||||
useEffect(() => {
|
||||
const svg = svgRef.current;
|
||||
if (!svg) return;
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
if (e.deltaY > 0 && vbRef.current.w >= W) return; // fully zoomed out: let the page scroll
|
||||
e.preventDefault();
|
||||
zoomAt(Math.exp(-e.deltaY * 0.0015), e.clientX, e.clientY);
|
||||
};
|
||||
svg.addEventListener("wheel", onWheel, { passive: false });
|
||||
return () => svg.removeEventListener("wheel", onWheel);
|
||||
}, []);
|
||||
|
||||
const onPointerDown = (e: PointerEvent<SVGSVGElement>) => {
|
||||
if (e.button !== 0) return;
|
||||
drag.current = { x: e.clientX, y: e.clientY, moved: false };
|
||||
};
|
||||
const onPointerMove = (e: PointerEvent<SVGSVGElement>) => {
|
||||
const d = drag.current;
|
||||
const rect = svgRef.current?.getBoundingClientRect();
|
||||
if (!d || !rect) return;
|
||||
const dx = e.clientX - d.x;
|
||||
const dy = e.clientY - d.y;
|
||||
if (!d.moved && Math.hypot(dx, dy) < 4) return; // a click, not a drag
|
||||
if (!d.moved) {
|
||||
// Capture only once it is a drag: capturing on pointerdown would retarget
|
||||
// pointerup (and so the click) to the <svg>, and a node's link would never fire.
|
||||
d.moved = true;
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
}
|
||||
d.x = e.clientX;
|
||||
d.y = e.clientY;
|
||||
const cur = vbRef.current;
|
||||
const scale = cur.w / rect.width;
|
||||
setVb({ ...cur, x: clamp(cur.x - dx * scale, 0, W - cur.w), y: clamp(cur.y - dy * scale, 0, H - cur.h) });
|
||||
};
|
||||
const onPointerUp = (e: PointerEvent<SVGSVGElement>) => {
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) e.currentTarget.releasePointerCapture(e.pointerId);
|
||||
// Cleared on the next tick so a node's click handler can still see `moved`.
|
||||
setTimeout(() => { drag.current = null; }, 0);
|
||||
};
|
||||
|
||||
const zoom = W / vb.w;
|
||||
const atHome = vb.x === HOME.x && vb.y === HOME.y && vb.w === HOME.w;
|
||||
|
||||
return (
|
||||
<figure
|
||||
className="not-prose"
|
||||
style={{ margin: "1.5rem 0", border: "1px solid var(--blume-border)", borderRadius: "var(--blume-radius)", background: tint("var(--blume-muted)", 30), overflow: "hidden" }}
|
||||
aria-label="Dependency spine: how the 24 topics build on each other"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "+" || e.key === "=") zoomAt(1.25);
|
||||
else if (e.key === "-") zoomAt(0.8);
|
||||
else if (e.key === "0") setVb(HOME);
|
||||
else return;
|
||||
e.preventDefault();
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: "0.75rem", padding: "0.5rem 0.75rem", borderBottom: "1px solid var(--blume-border)" }}>
|
||||
<span style={{ ...mono, fontSize: "0.7rem", letterSpacing: "0.12em", textTransform: "uppercase", color: muted, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
scroll to zoom · drag to pan · click a topic
|
||||
</span>
|
||||
<span style={{ display: "flex", alignItems: "center", gap: "0.35rem", flexShrink: 0 }}>
|
||||
<span style={{ ...mono, fontSize: "0.7rem", color: muted, minWidth: "4ch", textAlign: "right", whiteSpace: "nowrap" }}>{zoom.toFixed(1)}×</span>
|
||||
<button type="button" style={toolButton} aria-label="Zoom out" onClick={() => zoomAt(0.8)} disabled={zoom <= 1.001}>−</button>
|
||||
<button type="button" style={toolButton} aria-label="Zoom in" onClick={() => zoomAt(1.25)} disabled={zoom >= MAX_ZOOM - 0.001}>+</button>
|
||||
<button type="button" style={toolButton} aria-label="Reset zoom" onClick={() => setVb(HOME)} disabled={atHome}>⟲</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<svg
|
||||
ref={svgRef}
|
||||
viewBox={`${vb.x} ${vb.y} ${vb.w} ${vb.h}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
aria-labelledby="dependency-spine-title dependency-spine-desc"
|
||||
style={{ display: "block", width: "100%", height: "auto", touchAction: "none", cursor: drag.current?.moved ? "grabbing" : zoom > 1.001 ? "grab" : "default", userSelect: "none" }}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerCancel={onPointerUp}
|
||||
onDoubleClick={() => setVb(HOME)}
|
||||
>
|
||||
<title id="dependency-spine-title">LeetCode topic dependency spine</title>
|
||||
<desc id="dependency-spine-desc">
|
||||
Dependency graph of 24 LeetCode study topics across five phases, showing which topics build on which, with Tree DFS and 1-D dynamic programming as the two central hubs.
|
||||
</desc>
|
||||
<defs>
|
||||
<marker id="spine-arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" style={{ fill: muted }} />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
{ZONES.map(([label, x, y, w, h, plate]) => (
|
||||
<g key={label}>
|
||||
<rect x={x} y={y} width={w} height={h} rx={8} style={{ fill: tint(fg, 2), stroke: tint(fg, 10), strokeWidth: 0.8 }} />
|
||||
<rect x={x + 16} y={y + 4} width={plate} height={12} rx={2} style={{ fill: "var(--blume-background)" }} />
|
||||
<text x={x + 20} y={y + 13} fontSize={7} letterSpacing="0.14em" style={{ ...mono, fill: tint(fg, 40) }}>{label}</text>
|
||||
</g>
|
||||
))}
|
||||
|
||||
{EDGES.map(([id, d]) => (
|
||||
<path key={id} d={d} fill="none" strokeWidth={1} markerEnd="url(#spine-arrow)" style={{ stroke: muted }}>
|
||||
<title>{id}</title>
|
||||
</path>
|
||||
))}
|
||||
|
||||
{TOPICS.map((t) => {
|
||||
const x = COL_X[t.col]!;
|
||||
const y = ROW_Y[t.row]!;
|
||||
const s = NODE_STYLE[t.kind];
|
||||
const route = routes?.[`/${t.slug}`];
|
||||
const tag = String(t.n).padStart(2, "0");
|
||||
const body = (
|
||||
<>
|
||||
<title>{route ? `${tag} · ${t.name} — open topic` : `${tag} · ${t.name} — no pages yet`}</title>
|
||||
<rect x={x} y={y} width={NODE_W} height={NODE_H} rx={6} style={{ fill: "var(--blume-background)" }} />
|
||||
<rect x={x} y={y} width={NODE_W} height={NODE_H} rx={6} style={{ fill: s.fill, stroke: s.stroke, strokeWidth: s.strokeWidth }} />
|
||||
<rect x={x + 8} y={y + 6} width={24} height={12} rx={2} fill="transparent" style={{ stroke: tint(s.tag, 50), strokeWidth: 0.8 }} />
|
||||
<text x={x + 20} y={y + 15} fontSize={7} textAnchor="middle" letterSpacing="0.08em" style={{ ...mono, fill: s.tag }}>{tag}</text>
|
||||
<text x={x + NODE_W / 2} y={y + (t.sub ? 26 : 32)} fontSize={12} fontWeight={600} textAnchor="middle" style={{ ...sans, fill: fg }}>{t.name}</text>
|
||||
{t.sub && <text x={x + NODE_W / 2} y={y + 40} fontSize={9} textAnchor="middle" style={{ ...mono, fill: muted }}>{t.sub}</text>}
|
||||
</>
|
||||
);
|
||||
return route ? (
|
||||
<a
|
||||
key={t.n}
|
||||
href={withBase(route)}
|
||||
className="spine-node"
|
||||
style={{ cursor: "pointer", textDecoration: "none" }}
|
||||
onClick={(e) => { if (drag.current?.moved) e.preventDefault(); }}
|
||||
>
|
||||
{body}
|
||||
</a>
|
||||
) : (
|
||||
<g key={t.n} style={{ opacity: routes ? 0.55 : 1 }} aria-disabled="true">{body}</g>
|
||||
);
|
||||
})}
|
||||
|
||||
<line x1={40} y1={600} x2={1432} y2={600} strokeWidth={0.8} style={{ stroke: tint(fg, 10) }} />
|
||||
<text x={40} y={620} fontSize={8} letterSpacing="0.14em" style={{ ...mono, fill: muted }}>LEGEND</text>
|
||||
<rect x={160} y={608} width={20} height={12} rx={2} style={{ fill: NODE_STYLE.entry.fill, stroke: NODE_STYLE.entry.stroke, strokeWidth: 1 }} />
|
||||
<text x={188} y={618} fontSize={8} letterSpacing="0.06em" style={{ ...mono, fill: muted }}>ENTRY — NO PREREQS</text>
|
||||
<rect x={360} y={608} width={20} height={12} rx={2} style={{ fill: NODE_STYLE.topic.fill, stroke: NODE_STYLE.topic.stroke, strokeWidth: 1 }} />
|
||||
<text x={388} y={618} fontSize={8} letterSpacing="0.06em" style={{ ...mono, fill: muted }}>TOPIC</text>
|
||||
<rect x={480} y={608} width={20} height={12} rx={2} style={{ fill: NODE_STYLE.focal.fill, stroke: NODE_STYLE.focal.stroke, strokeWidth: 1.2 }} />
|
||||
<text x={508} y={618} fontSize={8} letterSpacing="0.06em" style={{ ...mono, fill: muted }}>FOCAL HUB</text>
|
||||
<line x1={624} y1={614} x2={656} y2={614} strokeWidth={1} markerEnd="url(#spine-arrow)" style={{ stroke: muted }} />
|
||||
<text x={668} y={618} fontSize={8} letterSpacing="0.06em" style={{ ...mono, fill: muted }}>BUILDS ON</text>
|
||||
<rect x={792} y={608} width={20} height={12} rx={2} style={{ fill: tint(fg, 2), stroke: tint(fg, 10), strokeWidth: 0.8 }} />
|
||||
<text x={820} y={618} fontSize={8} letterSpacing="0.06em" style={{ ...mono, fill: muted }}>PHASE ZONE</text>
|
||||
<rect x={1000} y={608} width={20} height={12} rx={2} style={{ fill: NODE_STYLE.topic.fill, stroke: NODE_STYLE.topic.stroke, strokeWidth: 1, opacity: 0.55 }} />
|
||||
<text x={1028} y={618} fontSize={8} letterSpacing="0.06em" style={{ ...mono, fill: muted }}>NO PAGES YET</text>
|
||||
</svg>
|
||||
<style>{`.spine-node:hover rect:nth-of-type(2), .spine-node:focus-visible rect:nth-of-type(2) { stroke: ${accent}; stroke-width: 1.6; } .spine-node:focus { outline: none; }`}</style>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
@@ -1,305 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LeetCode Dependency Spine</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #020202;
|
||||
--color-ink: #f2f2f2;
|
||||
--color-muted: #989898;
|
||||
--color-accent: #449df0;
|
||||
--font-sans: 'Inter', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'IBM Plex Mono', ui-monospace, monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1360px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--font-serif);
|
||||
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
color: var(--color-ink);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 900px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">prdlk / leetcode · dependency spine</p>
|
||||
<h1>How the 24 topics build on each other</h1>
|
||||
|
||||
<svg viewBox="0 0 1472 648" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dependency-spine-title dependency-spine-desc">
|
||||
<title id="dependency-spine-title">LeetCode topic dependency spine</title>
|
||||
<desc id="dependency-spine-desc">Dependency graph of 24 LeetCode study topics across five phases, showing which topics build on which, with Tree DFS and 1-D dynamic programming as the two central hubs.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#989898"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#449df0"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#6ab0f5"/></marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#020202"/>
|
||||
|
||||
<!-- ============ zones (painted first) ============ -->
|
||||
<rect x="40" y="40" width="368" height="312" rx="8" fill="rgba(242,242,242,0.02)" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<rect x="56" y="44" width="168" height="12" rx="2" fill="#020202"/>
|
||||
<text x="60" y="53" fill="rgba(242,242,242,0.40)" font-size="7" font-family="'IBM Plex Mono', monospace" letter-spacing="0.14em">PHASE I — LINEAR STRUCTURES</text>
|
||||
|
||||
<rect x="456" y="40" width="368" height="312" rx="8" fill="rgba(242,242,242,0.02)" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<rect x="472" y="44" width="152" height="12" rx="2" fill="#020202"/>
|
||||
<text x="476" y="53" fill="rgba(242,242,242,0.40)" font-size="7" font-family="'IBM Plex Mono', monospace" letter-spacing="0.14em">PHASE III — HIERARCHICAL</text>
|
||||
|
||||
<rect x="456" y="384" width="368" height="168" rx="8" fill="rgba(242,242,242,0.02)" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<rect x="472" y="388" width="144" height="12" rx="2" fill="#020202"/>
|
||||
<text x="476" y="397" fill="rgba(242,242,242,0.40)" font-size="7" font-family="'IBM Plex Mono', monospace" letter-spacing="0.14em">PHASE II — NODAL & GRID</text>
|
||||
|
||||
<rect x="872" y="40" width="560" height="312" rx="8" fill="rgba(242,242,242,0.02)" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<rect x="888" y="44" width="264" height="12" rx="2" fill="#020202"/>
|
||||
<text x="892" y="53" fill="rgba(242,242,242,0.40)" font-size="7" font-family="'IBM Plex Mono', monospace" letter-spacing="0.14em">PHASES IV–V — RELATIONAL & DECISION-SPACE</text>
|
||||
|
||||
<!-- ============ arrows (before boxes) ============ -->
|
||||
<!-- same-row horizontals -->
|
||||
<line x1="200" y1="168" x2="248" y2="168" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 3 → 4 -->
|
||||
<line x1="200" y1="240" x2="248" y2="240" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 6 → 7 -->
|
||||
<line x1="200" y1="312" x2="248" y2="312" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 1 → 2 -->
|
||||
<line x1="616" y1="440" x2="664" y2="440" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 8 → 9 -->
|
||||
<line x1="1032" y1="96" x2="1080" y2="96" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 16 → 18 -->
|
||||
<line x1="1224" y1="240" x2="1272" y2="240" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 21 → 22 -->
|
||||
<line x1="1224" y1="312" x2="1272" y2="312" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 23 → 24 -->
|
||||
<line x1="616" y1="84" x2="888" y2="84" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 11 → 16 -->
|
||||
<line x1="616" y1="312" x2="888" y2="312" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 12 → 17 -->
|
||||
<!-- same-column verticals -->
|
||||
<line x1="544" y1="120" x2="544" y2="144" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 11 → 13 -->
|
||||
<line x1="960" y1="120" x2="960" y2="144" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 16 → 19 -->
|
||||
<line x1="1152" y1="120" x2="1152" y2="144" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 18 → 20 -->
|
||||
<line x1="1152" y1="192" x2="1152" y2="216" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 20 → 21 -->
|
||||
<line x1="1152" y1="264" x2="1152" y2="288" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 21 → 23 -->
|
||||
<!-- rounded right-angle elbows -->
|
||||
<path d="M200,96 H416 Q424,96 424,104 V160 Q424,168 432,168 H472" fill="none" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 5 → 13 -->
|
||||
<path d="M616,96 H640 Q648,96 648,104 V160 Q648,168 656,168 H664" fill="none" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 11 → 14 -->
|
||||
<path d="M616,108 H624 Q632,108 632,116 V232 Q632,240 640,240 H664" fill="none" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 11 → 15 -->
|
||||
<path d="M392,184 H432 Q440,184 440,192 V504 Q440,512 448,512 H472" fill="none" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 4 → 10 -->
|
||||
<path d="M128,336 V560 Q128,568 136,568 H728 Q736,568 736,560 V464" fill="none" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 1 → 9 -->
|
||||
|
||||
<!-- ============ nodes ============ -->
|
||||
<!-- 05 Binary Search — entry -->
|
||||
<rect x="56" y="72" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="56" y="72" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="64" y="78" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="76" y="87" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">05</text>
|
||||
<text x="128" y="104" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Binary Search</text>
|
||||
|
||||
<!-- 03 Two Pointers — entry -->
|
||||
<rect x="56" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="56" y="144" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="64" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="76" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">03</text>
|
||||
<text x="128" y="176" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Two Pointers</text>
|
||||
|
||||
<!-- 04 Sliding Window -->
|
||||
<rect x="248" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="248" y="144" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="256" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="268" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">04</text>
|
||||
<text x="320" y="176" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Sliding Window</text>
|
||||
|
||||
<!-- 06 Stack — entry -->
|
||||
<rect x="56" y="216" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="56" y="216" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="64" y="222" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="76" y="231" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">06</text>
|
||||
<text x="128" y="248" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Stack</text>
|
||||
|
||||
<!-- 07 Monotonic Stack -->
|
||||
<rect x="248" y="216" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="248" y="216" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="256" y="222" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="268" y="231" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">07</text>
|
||||
<text x="320" y="248" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Monotonic Stack</text>
|
||||
|
||||
<!-- 01 Hash-Based Lookup — entry -->
|
||||
<rect x="56" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="56" y="288" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="64" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="76" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">01</text>
|
||||
<text x="128" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Hash Lookup</text>
|
||||
|
||||
<!-- 02 Prefix Sum -->
|
||||
<rect x="248" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="248" y="288" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="256" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="268" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">02</text>
|
||||
<text x="320" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Prefix Sum</text>
|
||||
|
||||
<!-- 11 Tree DFS — FOCAL -->
|
||||
<rect x="472" y="72" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="472" y="72" width="144" height="48" rx="6" fill="rgba(68,157,240,0.12)" stroke="#449df0" stroke-width="1.2"/>
|
||||
<rect x="480" y="78" width="24" height="12" rx="2" fill="transparent" stroke="rgba(68,157,240,0.50)" stroke-width="0.8"/>
|
||||
<text x="492" y="87" fill="#449df0" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">11</text>
|
||||
<text x="544" y="104" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Tree DFS</text>
|
||||
|
||||
<!-- 13 Binary Search Tree -->
|
||||
<rect x="472" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="472" y="144" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="480" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="492" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">13</text>
|
||||
<text x="544" y="176" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Binary Search Tree</text>
|
||||
|
||||
<!-- 14 Heap -->
|
||||
<rect x="664" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="664" y="144" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="672" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="684" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">14</text>
|
||||
<text x="736" y="170" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Heap</text>
|
||||
<text x="736" y="184" fill="#989898" font-size="9" font-family="'IBM Plex Mono', monospace" text-anchor="middle">priority queue</text>
|
||||
|
||||
<!-- 15 Trie -->
|
||||
<rect x="664" y="216" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="664" y="216" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="672" y="222" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="684" y="231" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">15</text>
|
||||
<text x="736" y="248" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Trie</text>
|
||||
|
||||
<!-- 12 Tree BFS — entry -->
|
||||
<rect x="472" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="472" y="288" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="480" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="492" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">12</text>
|
||||
<text x="544" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Tree BFS</text>
|
||||
|
||||
<!-- 08 Linked List — entry -->
|
||||
<rect x="472" y="416" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="472" y="416" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="480" y="422" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="492" y="431" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">08</text>
|
||||
<text x="544" y="448" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Linked List</text>
|
||||
|
||||
<!-- 09 Hybrid Structures -->
|
||||
<rect x="664" y="416" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="664" y="416" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="672" y="422" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="684" y="431" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">09</text>
|
||||
<text x="736" y="448" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Hybrid Structures</text>
|
||||
|
||||
<!-- 10 Matrix Index Math -->
|
||||
<rect x="472" y="488" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="472" y="488" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="480" y="494" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="492" y="503" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">10</text>
|
||||
<text x="544" y="520" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Matrix Index Math</text>
|
||||
|
||||
<!-- 16 Graph DFS -->
|
||||
<rect x="888" y="72" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="888" y="72" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="896" y="78" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="908" y="87" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">16</text>
|
||||
<text x="960" y="104" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Graph DFS</text>
|
||||
|
||||
<!-- 18 Topological Sort -->
|
||||
<rect x="1080" y="72" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1080" y="72" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="1088" y="78" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="1100" y="87" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">18</text>
|
||||
<text x="1152" y="104" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Topological Sort</text>
|
||||
|
||||
<!-- 19 Union-Find -->
|
||||
<rect x="888" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="888" y="144" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="896" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="908" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">19</text>
|
||||
<text x="960" y="176" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Union-Find</text>
|
||||
|
||||
<!-- 20 Backtracking -->
|
||||
<rect x="1080" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1080" y="144" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="1088" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="1100" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">20</text>
|
||||
<text x="1152" y="176" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Backtracking</text>
|
||||
|
||||
<!-- 21 1-D DP — FOCAL -->
|
||||
<rect x="1080" y="216" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1080" y="216" width="144" height="48" rx="6" fill="rgba(68,157,240,0.12)" stroke="#449df0" stroke-width="1.2"/>
|
||||
<rect x="1088" y="222" width="24" height="12" rx="2" fill="transparent" stroke="rgba(68,157,240,0.50)" stroke-width="0.8"/>
|
||||
<text x="1100" y="231" fill="#449df0" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">21</text>
|
||||
<text x="1152" y="242" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">1-D DP</text>
|
||||
<text x="1152" y="256" fill="#989898" font-size="9" font-family="'IBM Plex Mono', monospace" text-anchor="middle">dynamic programming</text>
|
||||
|
||||
<!-- 22 Multi-D / Grid DP -->
|
||||
<rect x="1272" y="216" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1272" y="216" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="1280" y="222" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="1292" y="231" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">22</text>
|
||||
<text x="1344" y="242" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Multi-D / Grid DP</text>
|
||||
|
||||
<!-- 17 Graph BFS -->
|
||||
<rect x="888" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="888" y="288" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="896" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="908" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">17</text>
|
||||
<text x="960" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Graph BFS</text>
|
||||
|
||||
<!-- 23 Greedy -->
|
||||
<rect x="1080" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1080" y="288" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="1088" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="1100" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">23</text>
|
||||
<text x="1152" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Greedy</text>
|
||||
|
||||
<!-- 24 Intervals -->
|
||||
<rect x="1272" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1272" y="288" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="1280" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="1292" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">24</text>
|
||||
<text x="1344" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Intervals</text>
|
||||
|
||||
<!-- ============ legend ============ -->
|
||||
<line x1="40" y1="600" x2="1432" y2="600" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="620" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.14em">LEGEND</text>
|
||||
|
||||
<rect x="160" y="608" width="20" height="12" rx="2" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<text x="188" y="618" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.06em">ENTRY — NO PREREQS</text>
|
||||
|
||||
<rect x="360" y="608" width="20" height="12" rx="2" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<text x="388" y="618" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.06em">TOPIC</text>
|
||||
|
||||
<rect x="480" y="608" width="20" height="12" rx="2" fill="rgba(68,157,240,0.12)" stroke="#449df0" stroke-width="1.2"/>
|
||||
<text x="508" y="618" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.06em">FOCAL HUB</text>
|
||||
|
||||
<line x1="624" y1="614" x2="656" y2="614" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<text x="668" y="618" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.06em">BUILDS ON</text>
|
||||
|
||||
<rect x="792" y="608" width="20" height="12" rx="2" fill="rgba(242,242,242,0.02)" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<text x="820" y="618" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.06em">PHASE ZONE</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,244 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 1472 648" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="dependency-spine-title dependency-spine-desc">
|
||||
<title id="dependency-spine-title">LeetCode topic dependency spine</title>
|
||||
<desc id="dependency-spine-desc">Dependency graph of 24 LeetCode study topics across five phases, showing which topics build on which, with Tree DFS and 1-D dynamic programming as the two central hubs.</desc>
|
||||
<defs>
|
||||
<style>@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500;600&display=swap');</style>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#989898"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#449df0"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#6ab0f5"/></marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#020202"/>
|
||||
|
||||
<!-- ============ zones (painted first) ============ -->
|
||||
<rect x="40" y="40" width="368" height="312" rx="8" fill="rgba(242,242,242,0.02)" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<rect x="56" y="44" width="168" height="12" rx="2" fill="#020202"/>
|
||||
<text x="60" y="53" fill="rgba(242,242,242,0.40)" font-size="7" font-family="'IBM Plex Mono', monospace" letter-spacing="0.14em">PHASE I — LINEAR STRUCTURES</text>
|
||||
|
||||
<rect x="456" y="40" width="368" height="312" rx="8" fill="rgba(242,242,242,0.02)" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<rect x="472" y="44" width="152" height="12" rx="2" fill="#020202"/>
|
||||
<text x="476" y="53" fill="rgba(242,242,242,0.40)" font-size="7" font-family="'IBM Plex Mono', monospace" letter-spacing="0.14em">PHASE III — HIERARCHICAL</text>
|
||||
|
||||
<rect x="456" y="384" width="368" height="168" rx="8" fill="rgba(242,242,242,0.02)" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<rect x="472" y="388" width="144" height="12" rx="2" fill="#020202"/>
|
||||
<text x="476" y="397" fill="rgba(242,242,242,0.40)" font-size="7" font-family="'IBM Plex Mono', monospace" letter-spacing="0.14em">PHASE II — NODAL & GRID</text>
|
||||
|
||||
<rect x="872" y="40" width="560" height="312" rx="8" fill="rgba(242,242,242,0.02)" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<rect x="888" y="44" width="264" height="12" rx="2" fill="#020202"/>
|
||||
<text x="892" y="53" fill="rgba(242,242,242,0.40)" font-size="7" font-family="'IBM Plex Mono', monospace" letter-spacing="0.14em">PHASES IV–V — RELATIONAL & DECISION-SPACE</text>
|
||||
|
||||
<!-- ============ arrows (before boxes) ============ -->
|
||||
<!-- same-row horizontals -->
|
||||
<line x1="200" y1="168" x2="248" y2="168" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 3 → 4 -->
|
||||
<line x1="200" y1="240" x2="248" y2="240" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 6 → 7 -->
|
||||
<line x1="200" y1="312" x2="248" y2="312" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 1 → 2 -->
|
||||
<line x1="616" y1="440" x2="664" y2="440" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 8 → 9 -->
|
||||
<line x1="1032" y1="96" x2="1080" y2="96" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 16 → 18 -->
|
||||
<line x1="1224" y1="240" x2="1272" y2="240" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 21 → 22 -->
|
||||
<line x1="1224" y1="312" x2="1272" y2="312" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 23 → 24 -->
|
||||
<line x1="616" y1="84" x2="888" y2="84" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 11 → 16 -->
|
||||
<line x1="616" y1="312" x2="888" y2="312" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 12 → 17 -->
|
||||
<!-- same-column verticals -->
|
||||
<line x1="544" y1="120" x2="544" y2="144" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 11 → 13 -->
|
||||
<line x1="960" y1="120" x2="960" y2="144" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 16 → 19 -->
|
||||
<line x1="1152" y1="120" x2="1152" y2="144" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 18 → 20 -->
|
||||
<line x1="1152" y1="192" x2="1152" y2="216" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 20 → 21 -->
|
||||
<line x1="1152" y1="264" x2="1152" y2="288" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 21 → 23 -->
|
||||
<!-- rounded right-angle elbows -->
|
||||
<path d="M200,96 H416 Q424,96 424,104 V160 Q424,168 432,168 H472" fill="none" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 5 → 13 -->
|
||||
<path d="M616,96 H640 Q648,96 648,104 V160 Q648,168 656,168 H664" fill="none" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 11 → 14 -->
|
||||
<path d="M616,108 H624 Q632,108 632,116 V232 Q632,240 640,240 H664" fill="none" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 11 → 15 -->
|
||||
<path d="M392,184 H432 Q440,184 440,192 V504 Q440,512 448,512 H472" fill="none" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 4 → 10 -->
|
||||
<path d="M128,336 V560 Q128,568 136,568 H728 Q736,568 736,560 V464" fill="none" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/> <!-- 1 → 9 -->
|
||||
|
||||
<!-- ============ nodes ============ -->
|
||||
<!-- 05 Binary Search — entry -->
|
||||
<rect x="56" y="72" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="56" y="72" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="64" y="78" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="76" y="87" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">05</text>
|
||||
<text x="128" y="104" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Binary Search</text>
|
||||
|
||||
<!-- 03 Two Pointers — entry -->
|
||||
<rect x="56" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="56" y="144" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="64" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="76" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">03</text>
|
||||
<text x="128" y="176" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Two Pointers</text>
|
||||
|
||||
<!-- 04 Sliding Window -->
|
||||
<rect x="248" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="248" y="144" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="256" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="268" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">04</text>
|
||||
<text x="320" y="176" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Sliding Window</text>
|
||||
|
||||
<!-- 06 Stack — entry -->
|
||||
<rect x="56" y="216" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="56" y="216" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="64" y="222" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="76" y="231" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">06</text>
|
||||
<text x="128" y="248" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Stack</text>
|
||||
|
||||
<!-- 07 Monotonic Stack -->
|
||||
<rect x="248" y="216" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="248" y="216" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="256" y="222" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="268" y="231" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">07</text>
|
||||
<text x="320" y="248" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Monotonic Stack</text>
|
||||
|
||||
<!-- 01 Hash-Based Lookup — entry -->
|
||||
<rect x="56" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="56" y="288" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="64" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="76" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">01</text>
|
||||
<text x="128" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Hash Lookup</text>
|
||||
|
||||
<!-- 02 Prefix Sum -->
|
||||
<rect x="248" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="248" y="288" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="256" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="268" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">02</text>
|
||||
<text x="320" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Prefix Sum</text>
|
||||
|
||||
<!-- 11 Tree DFS — FOCAL -->
|
||||
<rect x="472" y="72" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="472" y="72" width="144" height="48" rx="6" fill="rgba(68,157,240,0.12)" stroke="#449df0" stroke-width="1.2"/>
|
||||
<rect x="480" y="78" width="24" height="12" rx="2" fill="transparent" stroke="rgba(68,157,240,0.50)" stroke-width="0.8"/>
|
||||
<text x="492" y="87" fill="#449df0" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">11</text>
|
||||
<text x="544" y="104" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Tree DFS</text>
|
||||
|
||||
<!-- 13 Binary Search Tree -->
|
||||
<rect x="472" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="472" y="144" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="480" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="492" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">13</text>
|
||||
<text x="544" y="176" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Binary Search Tree</text>
|
||||
|
||||
<!-- 14 Heap -->
|
||||
<rect x="664" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="664" y="144" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="672" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="684" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">14</text>
|
||||
<text x="736" y="170" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Heap</text>
|
||||
<text x="736" y="184" fill="#989898" font-size="9" font-family="'IBM Plex Mono', monospace" text-anchor="middle">priority queue</text>
|
||||
|
||||
<!-- 15 Trie -->
|
||||
<rect x="664" y="216" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="664" y="216" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="672" y="222" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="684" y="231" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">15</text>
|
||||
<text x="736" y="248" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Trie</text>
|
||||
|
||||
<!-- 12 Tree BFS — entry -->
|
||||
<rect x="472" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="472" y="288" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="480" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="492" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">12</text>
|
||||
<text x="544" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Tree BFS</text>
|
||||
|
||||
<!-- 08 Linked List — entry -->
|
||||
<rect x="472" y="416" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="472" y="416" width="144" height="48" rx="6" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<rect x="480" y="422" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="492" y="431" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">08</text>
|
||||
<text x="544" y="448" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Linked List</text>
|
||||
|
||||
<!-- 09 Hybrid Structures -->
|
||||
<rect x="664" y="416" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="664" y="416" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="672" y="422" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="684" y="431" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">09</text>
|
||||
<text x="736" y="448" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Hybrid Structures</text>
|
||||
|
||||
<!-- 10 Matrix Index Math -->
|
||||
<rect x="472" y="488" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="472" y="488" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="480" y="494" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="492" y="503" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">10</text>
|
||||
<text x="544" y="520" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Matrix Index Math</text>
|
||||
|
||||
<!-- 16 Graph DFS -->
|
||||
<rect x="888" y="72" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="888" y="72" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="896" y="78" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="908" y="87" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">16</text>
|
||||
<text x="960" y="104" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Graph DFS</text>
|
||||
|
||||
<!-- 18 Topological Sort -->
|
||||
<rect x="1080" y="72" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1080" y="72" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="1088" y="78" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="1100" y="87" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">18</text>
|
||||
<text x="1152" y="104" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Topological Sort</text>
|
||||
|
||||
<!-- 19 Union-Find -->
|
||||
<rect x="888" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="888" y="144" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="896" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="908" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">19</text>
|
||||
<text x="960" y="176" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Union-Find</text>
|
||||
|
||||
<!-- 20 Backtracking -->
|
||||
<rect x="1080" y="144" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1080" y="144" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="1088" y="150" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="1100" y="159" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">20</text>
|
||||
<text x="1152" y="176" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Backtracking</text>
|
||||
|
||||
<!-- 21 1-D DP — FOCAL -->
|
||||
<rect x="1080" y="216" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1080" y="216" width="144" height="48" rx="6" fill="rgba(68,157,240,0.12)" stroke="#449df0" stroke-width="1.2"/>
|
||||
<rect x="1088" y="222" width="24" height="12" rx="2" fill="transparent" stroke="rgba(68,157,240,0.50)" stroke-width="0.8"/>
|
||||
<text x="1100" y="231" fill="#449df0" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">21</text>
|
||||
<text x="1152" y="242" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">1-D DP</text>
|
||||
<text x="1152" y="256" fill="#989898" font-size="9" font-family="'IBM Plex Mono', monospace" text-anchor="middle">dynamic programming</text>
|
||||
|
||||
<!-- 22 Multi-D / Grid DP -->
|
||||
<rect x="1272" y="216" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1272" y="216" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="1280" y="222" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="1292" y="231" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">22</text>
|
||||
<text x="1344" y="242" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Multi-D / Grid DP</text>
|
||||
|
||||
<!-- 17 Graph BFS -->
|
||||
<rect x="888" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="888" y="288" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="896" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="908" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">17</text>
|
||||
<text x="960" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Graph BFS</text>
|
||||
|
||||
<!-- 23 Greedy -->
|
||||
<rect x="1080" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1080" y="288" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="1088" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="1100" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">23</text>
|
||||
<text x="1152" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Greedy</text>
|
||||
|
||||
<!-- 24 Intervals -->
|
||||
<rect x="1272" y="288" width="144" height="48" rx="6" fill="#020202"/>
|
||||
<rect x="1272" y="288" width="144" height="48" rx="6" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<rect x="1280" y="294" width="24" height="12" rx="2" fill="transparent" stroke="rgba(242,242,242,0.30)" stroke-width="0.8"/>
|
||||
<text x="1292" y="303" fill="#989898" font-size="7" font-family="'IBM Plex Mono', monospace" text-anchor="middle" letter-spacing="0.08em">24</text>
|
||||
<text x="1344" y="320" fill="#f2f2f2" font-size="12" font-weight="600" font-family="'Inter', sans-serif" text-anchor="middle">Intervals</text>
|
||||
|
||||
<!-- ============ legend ============ -->
|
||||
<line x1="40" y1="600" x2="1432" y2="600" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="620" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.14em">LEGEND</text>
|
||||
|
||||
<rect x="160" y="608" width="20" height="12" rx="2" fill="rgba(152,152,152,0.10)" stroke="#6f6f6f" stroke-width="1"/>
|
||||
<text x="188" y="618" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.06em">ENTRY — NO PREREQS</text>
|
||||
|
||||
<rect x="360" y="608" width="20" height="12" rx="2" fill="#0d0d0d" stroke="#f2f2f2" stroke-width="1"/>
|
||||
<text x="388" y="618" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.06em">TOPIC</text>
|
||||
|
||||
<rect x="480" y="608" width="20" height="12" rx="2" fill="rgba(68,157,240,0.12)" stroke="#449df0" stroke-width="1.2"/>
|
||||
<text x="508" y="618" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.06em">FOCAL HUB</text>
|
||||
|
||||
<line x1="624" y1="614" x2="656" y2="614" stroke="#989898" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<text x="668" y="618" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.06em">BUILDS ON</text>
|
||||
|
||||
<rect x="792" y="608" width="20" height="12" rx="2" fill="rgba(242,242,242,0.02)" stroke="rgba(242,242,242,0.10)" stroke-width="0.8"/>
|
||||
<text x="820" y="618" fill="#989898" font-size="8" font-family="'IBM Plex Mono', monospace" letter-spacing="0.06em">PHASE ZONE</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
207. Course Schedule
|
||||
Difficulty: Medium
|
||||
https://leetcode.com/problems/course-schedule/
|
||||
|
||||
──────────────────────────────────────────────────
|
||||
|
||||
There are a total of numCourses courses you have to take, labeled
|
||||
from 0 to numCourses - 1. You are given an array prerequisites where
|
||||
prerequisites[i] = [ai, bi] indicates that you must take course bi
|
||||
first if you want to take course ai.
|
||||
|
||||
• For example, the pair [0, 1], indicates that to take course 0 you
|
||||
have to first take course 1.
|
||||
|
||||
Return true if you can finish all courses. Otherwise, return false.
|
||||
|
||||
|
||||
|
||||
Example 1:
|
||||
|
||||
Input: numCourses = 2, prerequisites = [[1,0]]
|
||||
Output: true
|
||||
Explanation: There are a total of 2 courses to take.
|
||||
To take course 1 you should have finished course 0. So it is possible.
|
||||
|
||||
Example 2:
|
||||
|
||||
Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
|
||||
Output: false
|
||||
Explanation: There are a total of 2 courses to take.
|
||||
To take course 1 you should have finished course 0, and to take
|
||||
course 0 you should also have finished course 1. So it is impossible.
|
||||
|
||||
|
||||
|
||||
Constraints:
|
||||
|
||||
• 1 <= numCourses <= 2000
|
||||
|
||||
• 0 <= prerequisites.length <= 5000
|
||||
|
||||
• prerequisites[i].length == 2
|
||||
|
||||
• 0 <= ai, bi < numCourses
|
||||
|
||||
• All the pairs prerequisites[i] are unique.
|
||||
"""
|
||||
|
||||
|
||||
class Solution:
|
||||
def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool:
|
||||
indegree = [0] * numCourses
|
||||
adj = [[] for x in range(numCourses)]
|
||||
for prereq in prerequisites:
|
||||
adj[prereq[1]].append(prereq[0])
|
||||
indegree[prereq[0]] += 1
|
||||
|
||||
queue = []
|
||||
|
||||
for i in range(numCourses):
|
||||
if indegree[i] == 0:
|
||||
queue.append(i)
|
||||
|
||||
visited = 0
|
||||
while queue:
|
||||
node = queue.pop(0)
|
||||
visited += 1
|
||||
for neighbor in adj[node]:
|
||||
indegree[neighbor] -= 1
|
||||
if indegree[neighbor] == 0:
|
||||
queue.append(neighbor)
|
||||
|
||||
return numCourses == visited
|
||||
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
210. Course Schedule II
|
||||
Difficulty: Medium
|
||||
https://leetcode.com/problems/course-schedule-ii/
|
||||
|
||||
──────────────────────────────────────────────────
|
||||
|
||||
There are a total of numCourses courses you have to take, labeled
|
||||
from 0 to numCourses - 1. You are given an array prerequisites where
|
||||
prerequisites[i] = [ai, bi] indicates that you must take course bi
|
||||
first if you want to take course ai.
|
||||
|
||||
• For example, the pair [0, 1], indicates that to take course 0 you
|
||||
have to first take course 1.
|
||||
|
||||
Return the ordering of courses you should take to finish all courses.
|
||||
If there are many valid answers, return any of them. If it is
|
||||
impossible to finish all courses, return an empty array.
|
||||
|
||||
|
||||
|
||||
Example 1:
|
||||
|
||||
Input: numCourses = 2, prerequisites = [[1,0]]
|
||||
Output: [0,1]
|
||||
Explanation: There are a total of 2 courses to take. To take course 1
|
||||
you should have finished course 0. So the correct course order is
|
||||
[0,1].
|
||||
|
||||
Example 2:
|
||||
|
||||
Input: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
|
||||
Output: [0,2,1,3]
|
||||
Explanation: There are a total of 4 courses to take. To take course 3
|
||||
you should have finished both courses 1 and 2. Both courses 1 and 2
|
||||
should be taken after you finished course 0.
|
||||
So one correct course order is [0,1,2,3]. Another correct ordering is
|
||||
[0,2,1,3].
|
||||
|
||||
Example 3:
|
||||
|
||||
Input: numCourses = 1, prerequisites = []
|
||||
Output: [0]
|
||||
|
||||
|
||||
|
||||
Constraints:
|
||||
|
||||
• 1 <= numCourses <= 2000
|
||||
|
||||
• 0 <= prerequisites.length <= numCourses * (numCourses - 1)
|
||||
|
||||
• prerequisites[i].length == 2
|
||||
|
||||
• 0 <= ai, bi < numCourses
|
||||
|
||||
• ai != bi
|
||||
|
||||
• All the pairs [ai, bi] are distinct.
|
||||
"""
|
||||
|
||||
|
||||
class Solution:
|
||||
def findOrder(self, numCourses: int, prerequisites: list[list[int]]) -> list[int]:
|
||||
prereq = {c: [] for c in range(numCourses)}
|
||||
for crs, pre in prerequisites:
|
||||
prereq[crs].append(pre)
|
||||
|
||||
# course has 3 possible states:
|
||||
# visited -> crs has been added to output
|
||||
# visiting -> crs not added to output, but added to cycle
|
||||
# unvisited -> crs not added to output nor cycle
|
||||
output = []
|
||||
visit, cycle = set(), set()
|
||||
|
||||
def dfs(crs):
|
||||
if crs in cycle:
|
||||
return False
|
||||
if crs in visit:
|
||||
return True
|
||||
|
||||
cycle.add(crs)
|
||||
for pre in prereq[crs]:
|
||||
if dfs(pre) == False:
|
||||
return False
|
||||
|
||||
cycle.remove(crs)
|
||||
visit.add(crs)
|
||||
output.append(crs)
|
||||
return True
|
||||
|
||||
for c in range(numCourses):
|
||||
if dfs(c) == False:
|
||||
return []
|
||||
|
||||
return output
|
||||