Compare commits

..
Author SHA1 Message Date
Prad Nukala ea44a99c40 feat(work): add solution for LeetCode 36 Valid Sudoku 2026-10-13 15:34:15 -04:00
Prad Nukala 00fa322a14 feat(work): add solution for LeetCode 19 Remove Nth Node From End of List 2026-10-13 15:14:50 -04:00
Prad Nukala bf56e170a4 chore: remove sd 2026-10-13 14:29:10 -04:00
Prad Nukala 4cf976f683 feat(work): add solution for LeetCode 876 Middle of the Linked List 2026-10-13 14:25:36 -04:00
Prad Nukala ace1944786 docs: clarify /progress page header tab and fix apps/docs table formatting 2026-10-13 09:16:41 -04:00
Prad Nukala 13734e2e27 docs: add /progress page with live stats cards and update navigation and component imports 2026-10-13 09:16:30 -04:00
Prad Nukala cb4a03b6fa feat: integrate system design 2026-10-07 14:18:14 -04:00
Prad Nukala e14432d32a docs(docs): improve system design guide and simplify custom page tabs 2026-10-07 14:17:38 -04:00
Prad Nukala 8843cb776c docs(AGENTS): fix apps/docs table row formatting 2026-10-07 13:45:26 -04:00
Prad Nukala 0e58b63d4d docs(docs): add header tabs for Changelog and Appendix pages 2026-10-07 13:45:21 -04:00
Prad Nukala b5c3f1a159 docs(readme): fix formatting of apps/docs entry in AGENTS.md 2026-10-07 13:00:01 -04:00
Prad Nukala 37d3d9ceaf feat(work): add Minimum Window Substring & Basic Calculator solutions and implement StockSpanner algorithm 2026-10-07 12:59:02 -04:00
Prad Nukala 5627d0ca64 docs(docs): add new problem documentation pages and source selector component 2026-10-07 12:58:59 -04:00
Prad Nukala 598688977a feat(work): add 901 online stock span solution and implement removeDuplicates algorithm 2026-10-06 13:47:23 -04:00
Prad Nukala a3e846c606 docs(docs): add documentation for Next Greater Element I and Remove Duplicates from Sorted Array II, update redirects 2026-10-06 13:47:20 -04:00
Prad Nukala 52d9654ade feat(array): add solution for problem 80 remove duplicates from sorted array ii 2026-10-06 13:16:50 -04:00
Prad Nukala f202c3a0a1 docs(docs): add two‑pointer guide with decision tree and illustrations 2026-10-06 13:16:45 -04:00
Prad Nukala 030887c7df docs(docs): add detailed stack overview with operation examples and SVG illustrations 2026-10-06 12:58:52 -04:00
Prad Nukala 733c71d364 feat(work): add solution for Next Greater Element I 2026-10-06 12:53:56 -04:00
Prad Nukala 115b901fbe docs(docs): add YouTube video and detailed monotonic stack explanation with Python example 2026-10-06 12:53:26 -04:00
37 changed files with 1770 additions and 62 deletions

No files matched your search

+3 -3
View File
@@ -38,7 +38,7 @@ flowchart LR
- **Determinism = idempotency.** Drill/gate sampling uses a seeded PRNG (`rng()` in `apps/api/src/srs.ts`, FNV-1a → mulberry32, seed = date / ISO week); the digest is keyed by ET date in `email_log`; one-tap links are unique on (problem, date, kind).
- **DST-proof crons:** each event has two UTC crons; code fires only on the computed ET hour (`etHour`), unit-checked in `apps/api/src/srs.test.ts`.
- **Chained workflows:** `GITHUB_TOKEN`-driven issue closes fire no `issues` events, so `close-topics.yml` chains off `workflow_run` of Close Solved instead. The Worker's webhook uses its own `GH_PAT`, so its events flow normally.
- **One "finished topic" rule, two consumers.** `apps/cli/topics.ts` (`coreSet()`: every `set:core` sub-issue closed, zero core = never) is read by `close-topics.ts` (closes the topic issue) and `release-topics.ts` (the `release` job of `close-solved.yml`). The release reads the sub-issues, not the topic issue, so the push that finishes a topic releases it in the same run instead of waiting for Close Topics to chain after it. Version = [timever](https://gist.github.com/twolfson/de1b004dd22536b8e668) (`YYYYMMDD.HHMMSS.NNNNNNNNN`, UTC) of the instant the last core problem closed; the tag is annotated and backdated to that instant, on the last first-parent main commit before it; the body is a changelog snapshot at that instant (a one-sentence summary first, then the problems closed on the finish day / earlier, with solution links at the tag — problems still open at the finish are left out). A release is found by its `<!-- release-topics: #N -->` body marker, never by tag name, so re-closing a core problem never mints a second one; an existing release keeps its tag and has its name (the topic title) and body rewritten whenever the render changes. The docs `/changelog` is ONE custom page, `apps/docs/pages/changelog.astro` (a NeonUI-style timeline: date left, a card per release right, no per-release subpages, so nothing of it enters the sidebar or prev/next), linked from the header by `navigation.actions` (not a tab). `apps/docs/lib/releases.ts` fetches the releases at build (`GITHUB_TOKEN` in `deploy.yml`; a failed fetch fails the build), dates each by its timever tag, not `published_at` (which the API cannot backdate), tags it `Topic` or `Review` from the release name, and lists the rows of its `### Closed …` tables — so the tag format and that table shape are the page's contract with `render()`. There is no changelog RSS feed.
- **One "finished topic" rule, two consumers.** `apps/cli/topics.ts` (`coreSet()`: every `set:core` sub-issue closed, zero core = never) is read by `close-topics.ts` (closes the topic issue) and `release-topics.ts` (the `release` job of `close-solved.yml`). The release reads the sub-issues, not the topic issue, so the push that finishes a topic releases it in the same run instead of waiting for Close Topics to chain after it. Version = [timever](https://gist.github.com/twolfson/de1b004dd22536b8e668) (`YYYYMMDD.HHMMSS.NNNNNNNNN`, UTC) of the instant the last core problem closed; the tag is annotated and backdated to that instant, on the last first-parent main commit before it; the body is a changelog snapshot at that instant (a one-sentence summary first, then the problems closed on the finish day / earlier, with solution links at the tag — problems still open at the finish are left out). A release is found by its `<!-- release-topics: #N -->` body marker, never by tag name, so re-closing a core problem never mints a second one; an existing release keeps its tag and has its name (the topic title) and body rewritten whenever the render changes. The docs `/changelog` is ONE custom page, `apps/docs/pages/changelog.astro` (a NeonUI-style timeline: date left, a card per release right, no per-release subpages, so nothing of it enters the sidebar or prev/next), linked from the header by a `navigation.tabs` entry (beside `/appendix`'s). `apps/docs/lib/releases.ts` fetches the releases at build (`GITHUB_TOKEN` in `deploy.yml`; a failed fetch fails the build), dates each by its timever tag, not `published_at` (which the API cannot backdate), tags it `Topic` or `Review` from the release name, and lists the rows of its `### Closed …` tables — so the tag format and that table shape are the page's contract with `render()`. There is no changelog RSS feed.
- **Review issues re-parent problems; topics still own them.** The transfer phase's 20 `Review <N> — <Phase>` issues (label `review`, milestone `Review — Transfer & Mocks`; not the Worker's `Review — Week N` gate issues) hold open optional/deferred problems as sub-issues in solve order, moved there with `replace_parent`. `apps/api/src/reviews.ts` is the ONE recovery of ownership — `readReviews()` (one GraphQL request per 50 `review` issues, cheap enough for the Worker's cron subrequest budget) reads each moved problem's topic off its last `parent_issue_removed` event naming a topic, `movedFromTopics()` groups them — shared by the Worker's catalog reconcile (a moved problem's D1 row keeps syncing its labels and `topic_issue`) and `apps/cli/topics.ts` (`readProblems()` returns a topic's sub-issues plus its moved problems, so the finished rule and existing release bodies do not change when a problem moves). A Review issue finishes by `completion()` in `topics.ts` — every sub-issue closed, any set — which `close-topics.ts` closes it on and `release-topics.ts` releases it on, exactly like a topic: timever of the last close, same `<!-- release-topics: #N -->` marker (issue numbers never collide), body fields from the review's `**Scheduled:**`/`**Phase:**`/`**Load:**` lines.
- **Review days are plan days.** `schedule.json` books each Review issue on its own date — Mon/Tue/Thu/Fri only, Oct 1 → Nov 3 — as `review: { issue, title, problems }` (solve order). The digest renders it as a linked "Today's review" section with the issue's day rules (`REVIEW_RULES` in `email.tsx`, one sentence for HTML and text), its problems join `RESERVED` so drills and sampled gates never draw them early, and `gate: []` skips a Saturday's checkpoint (Oct 3). The issue's sub-issues stay the truth: the catalog reconcile compares every booked review's title and problem list with GitHub and returns any mismatch as `reviewDrift` (also logged), so a re-arranged Review issue shows up the next sync instead of silently disagreeing with the email. Move a review by editing its `schedule.json` date AND the issue's `**Scheduled:**` line (the release body reads it).
@@ -48,7 +48,7 @@ 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/`, `pages/`, `components/` + `components.ts`, `islands/`, `public/`). `content/(0N-<phase>)/<topic>/(problems)/<num>_<slug>.mdx` plus one `meta.ts` per phase and per topic folder, all generated by sync (a phase's once, then hand-owned): the phase folder is a `(group)` (sidebar section, no route segment), the topic folder is the README campaign topic slug (`tree-dfs/`, `heap-priority-queue/`) and a real route segment, and `(problems)` is a second `(group)` inside it, so `tree-dfs/(problems)/226_invert-binary-tree.mdx` routes to `/tree-dfs/invert-binary-tree` (Blume sorts by the `<num>_` prefix and strips it from the route). The `meta.ts` files carry the exact labels, phase/topic numbers and icons so the sidebar reads in curriculum order; every topic meta is `display: "page"`, so a topic is one sidebar row whose panel holds its landing page and a "Problems" section. A problem page's badge row (between the description and the body) is rendered, not written: the MDX `wrapper` (`components/ProblemBadges.astro`, registered as `mdx.wrapper` in `components.ts` — Blume's page template renders the title and description itself with no slot before the body, so the wrapper, which MDX hands no route, reads it off `Astro.url`) reads the page's `topics:` (LeetCode's topic tags, from sync) and `video:` (NeetCode YouTube id, from `bun run videos`) frontmatter — both declared via `frontmatter.extend` in `blume.config.ts`, hand-rolled Standard Schemas, no zod — and links a tag the campaign teaches to its topic landing page (filled), any other tag to its card on the Appendix glossary `/appendix#<slug>` (outlined; LeetCode's problem list if the tag has no term, gray and iconless), and the video as a red `Explanation` badge. Every badge in the row is a pill. A tag badge carries the icon its target shows (a landing's: the topic `meta.ts` icon read off `data.navigation`; an Appendix term's: its `icon` in `appendix.ts`) and the tag's own colour from `lib/tag-colors.ts` — unique per tag site-wide, assigned greedily over every tag with a landing or a term so tags that share problem pages sit furthest apart in OKLab, in a lightness band that keeps ≥ 3:1 contrast in both themes (Blume's Badge takes one hex for both). The reverse direction is the `PageFooter` slot (`components/BacklinksList.astro`): an Obsidian-style `<num> <name>` list under every topic landing page of the problems whose badges link there, plus a topic's own folder members (so a topic no tag names, `hybrid-structures`, still lists its problems), in a native `<details>` open on load whose top-right chevron collapses it (no JS); problem pages are recognised by their `<num>. <name>` title and render no list. Which tags the campaign teaches is ONE map, `apps/docs/topic-landings.ts` (tag slug → landing routes), and where a badge points is ONE function beside it, `tagTarget()` — read by both components and the Appendix page (so badges, backlinks and term cards cannot disagree) and the map by sync. Every other tag a problem carries is ONE hand-written table, `apps/docs/appendix.ts` (`APPENDIX`: slug → `{ name, icon, category, definition }`), rendered by ONE custom page, `pages/appendix.astro` — a glossary with an A–Z letter index and a filter box (an `<appendix-filter>` custom element over static markup; no JS, the full list), one card per term (icon tinted the tag's colour, category, definition, LeetCode list link, and a `<details>` "Solved" list of the problem pages whose badge `tagTarget()` sends there, paged `PAGE_SIZE` (5) rows at a time by numbered buttons under it — a `<solved-pages>` custom element; without JS every row shows and the buttons stay hidden), each card `id`'d by slug and highlighted as `:target`. It is no content folder, so it enters neither the sidebar tree nor prev/next: `navigation.featured` pins it above the tree. Sync writes nothing for it — it warns about a carried tag with no term, a term whose tag a topic now teaches (delete it), and a term `icon` that is a `TOPIC_ICONS` value or another term's. The former per-term pages `/appendix/<slug>` are exact redirects to `/appendix#<slug>` in `blume.config.ts`, generated from `APPENDIX`'s keys. A hand-written `(0N-<phase>)/<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; the five `/progress` cards `SolvedRing`/`SubmissionHeatmap`/`SubmissionChart`/`TopicBubbles`/`RecentSolves`, and `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 aLine truncated
| `apps/docs/` | Blume site (`blume.config.ts`, `content/`, `pages/`, `components/` + `components.ts`, `islands/`, `public/`). `content/(0N-<phase>)/<topic>/(problems)/<num>_<slug>.mdx` plus one `meta.ts` per phase and per topic folder, all generated by sync (a phase's once, then hand-owned): the phase folder is a `(group)` (sidebar section, no route segment), the topic folder is the README campaign topic slug (`tree-dfs/`, `heap-priority-queue/`) and a real route segment, and `(problems)` is a second `(group)` inside it, so `tree-dfs/(problems)/226_invert-binary-tree.mdx` routes to `/tree-dfs/invert-binary-tree` (Blume sorts by the `<num>_` prefix and strips it from the route). The `meta.ts` files carry the exact labels, phase/topic numbers and icons so the sidebar reads in curriculum order; every topic meta is `display: "page"`, so a topic is one sidebar row whose panel holds its landing page and a "Problems" section. A problem page's badge row (between the description and the body) is rendered, not written: the MDX `wrapper` (`components/ProblemBadges.astro`, registered as `mdx.wrapper` in `components.ts` — Blume's page template renders the title and description itself with no slot before the body, so the wrapper, which MDX hands no route, reads it off `Astro.url`) reads the page's `topics:` (LeetCode's topic tags, from sync) and `video:` (NeetCode YouTube id, from `bun run videos`) frontmatter — both declared via `frontmatter.extend` in `blume.config.ts`, hand-rolled Standard Schemas, no zod — and links a tag the campaign teaches to its topic landing page (filled), any other tag to its card on the Appendix glossary `/appendix#<slug>` (outlined; LeetCode's problem list if the tag has no term, gray and iconless), and the video as a red `Explanation` badge. Every badge in the row is a pill. A tag badge carries the icon its target shows (a landing's: the topic `meta.ts` icon read off `data.navigation`; an Appendix term's: its `icon` in `appendix.ts`) and the tag's own colour from `lib/tag-colors.ts` — unique per tag site-wide, assigned greedily over every tag with a landing or a term so tags that share problem pages sit furthest apart in OKLab, in a lightness band that keeps ≥ 3:1 contrast in both themes (Blume's Badge takes one hex for both). The reverse direction is the `PageFooter` slot (`components/BacklinksList.astro`): an Obsidian-style `<num> <name>` list under every topic landing page of the problems whose badges link there, plus a topic's own folder members (so a topic no tag names, `hybrid-structures`, still lists its problems), in a native `<details>` open on load whose top-right chevron collapses it (no JS); problem pages are recognised by their `<num>. <name>` title and render no list. The sidebar is Blume's default nav tree (no `Sidebar` slot override). The custom pages `/progress`, `/changelog` and `/appendix` are header `navigation.tabs` (on mobile, Blume lists them at the top of the drawer); never `navigation.featured`, which Blume renders above the nav tree. Which tags the campaign teaches is ONE map, `apps/docs/topic-landings.ts` (tag slug → landing routes), and where a badge points is ONE function beside it, `tagTarget()` — read by both components and the Appendix page (so badges, backlinks and term cards cannot disagree) and the map by sync. Every other tag a problem carries is ONE hand-written table, `apps/docs/appendix.ts` (`APPENDIX`: slug → `{ name, icon, category, definition }`), rendered by ONE custom page, `pages/appendix.astro` — a glossary with an A–Z letter index and a filter box (an `<appendix-filter>` custom element over static markup; no JS, the full list), one card per term (icon tinted the tag's colour, category, definition, LeetCode list link, and a `<details>` "Solved" list of the problem pages whose badge `tagTarget()` sends there, paged `PAGE_SIZE` (5) rows at a time by numbered buttons under it — a `<solved-pages>` custom element; without JS every row shows and the buttons stay hidden), each card `id`'d by slug and highlighted as `:target`. It is no content folder, so it enters neither the sidebar tree nor prev/next: its header tab is its link. Sync writes nothing for it — it warns about a carried tag with no term, a term whose tag a topic now teaches (delete it), and a term `icon` that is a `TOPIC_ICONS` value or another term's. The former per-term pages `/appendix/<slug>` are exact redirects to `/appendix#<slug>` in `blume.config.ts`, generated from `APPENDIX`'s keys. A hand-written `(0N-<phase>)/<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; `DependencySpine` — the README topic map as data, zoom/pan, nodes link to whatever route Blume's `/api/docs/navigaLine truncated
| `apps/api/` | Cloudflare Worker workspace: `src/` modules, `data/schedule.json` (the plan: `date → { topics | problems | review | gate | sweep }`, human-edited, bundled at deploy), `migrations/` (append-only; `0002` is the +3/+7 ladder rebuild) |
| `.github/workflows/` | deploy (docs → Pages with `GITHUB_TOKEN` for the releases-backed changelog, Worker → Cloudflare with the `OPENROUTER_API_KEY` Actions secret put into it first, on every main push), close-solved (issues + `/admin/solved` + Project `Set`/`Difficulty`, needs `SRS_ADMIN_KEY` and `PROJECT_PAT`; then the `release` job: timever tag + release per finished topic, `contents: write`), 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-commit` runs `bun run sync` and stages only what sync changed (pages, `apps/docs/redirects.ts`), so a solution, its page and its `/<num>` short link 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 |
@@ -116,7 +116,7 @@ curl -X POST -H "Authorization: Bearer $LINK_KEY" \
- `apps/cli/sync.ts` — work→docs contract: on existing pages only the frontmatter `icon:` and the `## Solution` section are regenerated, and `topics:` (LeetCode's `topicTags` as `{ name, slug }`, fetched by GraphQL) and `issue:` (the URL of the problem's `problem`-labelled GitHub issue, `LC <num> · …`, listed via `github()` — rendered as the neutral `Issue #N` badge after `Explanation`) are filled once — on create, or on any run that finds them missing — never refreshed (delete a key to refetch; offline or without `gh` auth, the page is written without it and the next sync retries; a sync with nothing missing makes no request). Only a problem the README plan lists gets `issue:` — an unplanned warm-up has no catalog issue, so it is never looked up. `## Solution` is 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 `work/1/<Difficulty>/<Category>/` category, which only places an unplanned problem — the page's topic badges are LeetCode's tags, not either classification. Topics nest under their README phase (`## Phase N — Name` headings, shortened via `PHASE_ALIASES`): `content/(0N-<phase>)/<topic>/`, with every problem page in the topic's `(problems)/` group as `<num>_<slug>.mdx` (a page is found by its frontmatter title, wherever it sits, so a layout change is one sync of `rename`s). The topic `meta.ts` is script-owned and rewritten every run (label, curriculum order, `TOPIC_ICONS` icon, `display: "page"`); the phase `meta.ts` is written once when missing (`Phase N: Name`, `order: N`) and hand-owned after — rename its title freely. Never rewriting it loses nothing: its order and name are also the folder name, so a README change to either lands in a new folder with a fresh meta. The phase folder is a Blume `(group)` — no route segment, so `/tree-dfs` is unchanged and a landing page there is one level deep, never at `/` — and the topic folder is the real segment; never put a topic `index.mdx` directly inside a `(group)`. A page is `rename`d when its topic or phase changes; a folder every page has left is deleted (climbing through an emptied `(problems)` and topic into an emptied phase folder) unless it still holds a hand-written `index.mdx` (sync never writes, moves, or deletes one — so page-relative asset paths in a moved landing page are yours to fix). Slugs come from `slugify()`, which glues a leading number to the next token (`1d-dynamic-programming`) because Blume strips `<digits>-` prefixes from routes. The hand-owned parts 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); the frontmatter `video:` (the NeetCode walkthrough, rendered as the red `Explanation` badge) is added 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` / `apps/docs/lib/stats.ts` — the read side. `buildStats()` is the ONE aggregation: phases, ladder, difficulty, `attempting` (open misses, via `MISS_WHERE` in `srs.ts` — the sweep day's definition), per-topic temperature, gates, the 14-day due queue, `heat` (every campaign day, zeros and future days included — the TUI windows its tail rather than asking a second question), `recent` (the ten problems most recently solved, dated by each one's first pass) and `year` (the 365 days ending today — always the calendar year to date — each split by difficulty into attempts and first solves; the docs cards window and bucket it themselves). Two readers consume it, the docs `/progress` islands and `bun run stats`, so its shape is their contract: change the Worker and both readers together (additive fields a reader ignores are safe). `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 is five LeetCode-style islands — `SolvedRing` (difficulty gauge + "Attempting"), `SubmissionHeatmap` and `SubmissionChart` sharing one row (the heatmap: the last six months in month blocks, the README's colour ramp as theme.css `--heat-0..4`, its attempts / active days / max streak counted from the cells it draws; the chart: this calendar year as stacked Easy/Med./Hard bars per month or week, first solves or every attempt, via two shadcn `ToggleGroup`s), `TopicBubbles` (the campaign topics circle-packed by `d3-hierarchy`, filled by solved share), `RecentSolves` (the ten most recent, linked through `redirects.ts`) — composed from shadcn/ui (`components/ui/{card,chart,skeleton,toggle,toggle-group}.tsx`, Recharts under `chart`, Radix under the toggles) on Blume's tokens: `apps/docs/theme.css` maps shadcn's names onto `--blume-*` (never `accent`, which is Blume's brand colour — vendored components that use it, like `toggle.tsx`, are retokened to `background`/`muted`) and gives shadcn's `data-slot` parts their border colour. They share `lib/stats.ts` (`useStats()`), so the page makes ONE `/api/stats` request however many islands hydrate. shadcn sources live outside `islands/` (every `.tsx` there becomes an island) and import `lib/utils` relatively; `components.json` + `tsconfig.json` exist for the shadcn CLI and `tsc`, so after `shadcn add` rewrite `@/…` imports to relative ones (house style) — and check `package.json`: the CLI has twice installed an unrelated `cn` package for `lib/utils` and skipped real deps (`class-variance-authority`). Blume turns every tsconfig `paths` target into a Vite alias AND a dev dep-scan glob (`<target>/**/*.astro`…), so `paths` maps only `@/components`, `@/lib`, `@/hooks` — each bare and `/*`, since `components.json` names the bare forms — never `@/*` → `./*`: that glob reaches through `node_modules/blume`, where Vite's scanner reads a `<script>` inside a `RootLayout.astro` comment as a tag and `blume dev` skips dependency pre-bundling. Blume's `.prose :where(a, p, ol)` rules ignore `not-prose`, so island markup overrides them with `!` utilities, as Blume's own components do.
- `README.md` live charts are Worker endpoints (`/chart/*.svg`, `/badge/gate.svg`, 5-min Camo cache). The docs `/progress` page is a custom page, `apps/docs/pages/progress.astro` (no content folder, so outside the sidebar and prev/next; its header tab is its link), mounting five LeetCode-style React cards from `components/progress/` with `client:load` — imported by the page, never used from MDX, so they are not in `islands/` — `SolvedRing` (difficulty gauge + "Attempting"), `SubmissionHeatmap` and `SubmissionChart` sharing one row (the heatmap: the last six months in month blocks, the README's colour ramp as theme.css `--heat-0..4`, its attempts / active days / max streak counted from the cells it draws; the chart: this calendar year as stacked Easy/Med./Hard bars per month or week, first solves or every attempt, via two shadcn `ToggleGroup`s), `TopicBubbles` (the campaign topics circle-packed by `d3-hierarchy`, filled by solved share), `RecentSolves` (the ten most recent, linked through `redirects.ts`) — composed from shadcn/ui (`components/ui/{card,chart,skeleton,toggle,toggle-group}.tsx`, Recharts under `chart`, Radix under the toggles) on Blume's tokens: `apps/docs/theme.css` maps shadcn's names onto `--blume-*` (never `accent`, which is Blume's brand colour — vendored components that use it, like `toggle.tsx`, are retokened to `background`/`muted`) and gives shadcn's `data-slot` parts their border colour. They share `lib/stats.ts` (`useStats()`), so the page makes ONE `/api/stats` request however many cards hydrate. shadcn sources live outside `islands/` (every `.tsx` there becomes an island) and import `lib/utils` relatively; `components.json` + `tsconfig.json` exist for the shadcn CLI and `tsc`, so after `shadcn add` rewrite `@/…` imports to relative ones (house style) — and check `package.json`: the CLI has twice installed an unrelated `cn` package for `lib/utils` and skipped real deps (`class-variance-authority`). Blume turns every tsconfig `paths` target into a Vite alias AND a dev dep-scan glob (`<target>/**/*.astro`…), so `paths` maps only `@/components`, `@/lib`, `@/hooks` — each bare and `/*`, since `components.json` names the bare forms — never `@/*` → `./*`: that glob reaches through `node_modules/blume`, where Vite's scanner reads a `<script>` inside a `RootLayout.astro` comment as a tag and `blume dev` skips dependency pre-bundling. Blume's `.prose :where(a, p, ol)` rules ignore `not-prose`, so island markup overrides them with `!` utilities, as Blume's own components do.
- `apps/docs/blume.config.ts` — site base `/leetcode`; imports `apps/docs/redirects.ts`, sync-generated (`/<num>` → `/<topic>/<slug>`, one row per page, exact root-relative paths — Blume adds the base and, for the static Pages build, emits a meta-refresh page per rule plus `blume-redirects.json`); `README.md` campaign table doubles as topic-map input to `sync.ts` (`readmeTopics()`, `TOPIC_ALIASES`, `PHASE_ALIASES`).
- Old `.github/srs/` JSON state, its `srs-*` Actions, and the one-shot `import-srs.ts` importer are RETIRED and deleted — do not resurrect; git history is the record.
+11 -9
View File
@@ -127,10 +127,11 @@ export default defineConfig({
llmsTxt: true,
mcp: { enabled: false },
},
// The only content source. `/changelog` and `/appendix` are custom pages
// (pages/*.astro): `/changelog` reads the GitHub releases itself — one
// timeline, no per-release pages — and `/appendix` is one glossary page
// built from appendix.ts, so neither enters the sidebar tree or prev/next.
// The only content source. `/changelog`, `/appendix` and `/progress` are
// custom pages (pages/*.astro): `/changelog` reads the GitHub releases
// itself — one timeline, no per-release pages — `/appendix` is one glossary
// page built from appendix.ts, and `/progress` is the live stats cards, so
// none enters the sidebar tree or prev/next.
content: {
sources: [filesystem({ root: "content" })],
},
@@ -142,11 +143,12 @@ export default defineConfig({
sidebar: {
display: "flat", // "flat" | "group" | "page"
},
// The glossary is no content folder any more, so it is pinned above the
// sidebar tree, on every page and breakpoint.
featured: [
{ label: "Changelog", href: "/changelog", icon: "clock" },
{ label: "Appendix", href: "/appendix", icon: "library" },
// The custom pages (pages/*.astro) as header tabs. None is a content
// folder, so the tabs scope nothing out of the sidebar tree.
tabs: [
{ label: "Changelog", path: "/changelog" },
{ label: "Progress", path: "/progress" },
{ label: "Appendix", path: "/appendix" },
],
},
});
@@ -5,12 +5,10 @@
* hover. A row links to the problem's docs page when one exists: the
* sync-generated short links (`redirects.ts`) are exactly the pages built.
*/
import { StatsPending } from "../components/progress/shared";
import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card";
import { useStats, utcDate } from "../lib/stats";
import redirects from "../redirects";
export const client = "load";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { useStats, utcDate } from "../../lib/stats";
import redirects from "../../redirects";
import { StatsPending } from "./shared";
const PAGES = new Map(redirects.map((r) => [r.from, r.to]));
@@ -8,13 +8,11 @@
*/
import { useState } from "react";
import { Pie, PieChart } from "recharts";
import { StatsPending } from "../components/progress/shared";
import { Card, CardContent } from "../components/ui/card";
import { type ChartConfig, ChartContainer } from "../components/ui/chart";
import { DIFFICULTIES, type Difficulty, useStats } from "../lib/stats";
import { cn } from "../lib/utils";
export const client = "load";
import { Card, CardContent } from "../ui/card";
import { type ChartConfig, ChartContainer } from "../ui/chart";
import { DIFFICULTIES, type Difficulty, useStats } from "../../lib/stats";
import { cn } from "../../lib/utils";
import { StatsPending } from "./shared";
const chartConfig = {
easy: { label: "Easy", color: "var(--chart-1)" },
@@ -9,13 +9,11 @@
*/
import { useState } from "react";
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { StatsPending } from "../components/progress/shared";
import { Card, CardAction, CardContent, CardFooter, CardHeader } from "../components/ui/card";
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "../components/ui/chart";
import { ToggleGroup, ToggleGroupItem } from "../components/ui/toggle-group";
import { DIFFICULTIES, type Day, type Difficulty, useStats, utcDate } from "../lib/stats";
export const client = "load";
import { Card, CardAction, CardContent, CardFooter, CardHeader } from "../ui/card";
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "../ui/chart";
import { ToggleGroup, ToggleGroupItem } from "../ui/toggle-group";
import { DIFFICULTIES, type Day, type Difficulty, useStats, utcDate } from "../../lib/stats";
import { StatsPending } from "./shared";
const chartConfig = {
easy: { label: "Easy", color: "var(--chart-1)" },
@@ -8,11 +8,9 @@
* 4) and rows start on Monday like it, so the three heatmaps read the same.
* Drawn as SVG in a viewBox, so it scales to its column instead of scrolling.
*/
import { StatsPending, TipRow, useHoverTip } from "../components/progress/shared";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
import { type Day, useStats, utcDate } from "../lib/stats";
export const client = "load";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
import { type Day, useStats, utcDate } from "../../lib/stats";
import { StatsPending, TipRow, useHoverTip } from "./shared";
const MONTHS = 6;
const CELL = 10;
@@ -9,12 +9,10 @@
*/
import { packEnclose, packSiblings } from "d3-hierarchy";
import { useId } from "react";
import { StatsPending, TipRow, useHoverTip } from "../components/progress/shared";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
import { type Stats, useStats } from "../lib/stats";
import { cn } from "../lib/utils";
export const client = "load";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
import { type Stats, useStats } from "../../lib/stats";
import { cn } from "../../lib/utils";
import { StatsPending, TipRow, useHoverTip } from "./shared";
const PAD = 2; // gap between bubbles, in layout units
const UNIT = 12; // radius of a one-problem bubble
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* What the four progress islands share besides the data: the not-ready body
* What the progress cards share besides the data: the not-ready body
* (a skeleton while /api/stats loads, one muted line if it fails) and the
* hover tooltip for the two charts drawn as plain SVG (the heatmap calendar
* and the topic bubbles — Recharts has neither layout). The tooltip wears the
@@ -0,0 +1,59 @@
---
title: '496. Next Greater Element I'
description: The next greater element of some element x in an array is the first greater element that is to the right of x in the same array
icon: dot
topics:
- { name: "Array", slug: "array" }
- { name: "Hash Table", slug: "hash-table" }
- { name: "Stack", slug: "stack" }
- { name: "Monotonic Stack", slug: "monotonic-stack" }
issue: "https://github.com/prdlk/leetcode/issues/74"
sidebar:
label: 'Next Greater Element I'
badge: 'Easy'
---
::::warning
Could you find an O(nums1.length + nums2.length) solution?
::::
### Example 1:
- Input: `nums1 = [4,1,2], nums2 = [1,3,4,2]`
- Output: `[-1,3,-1]`
- Explanation:
- The next greater element for each value of `nums1` is as follows:
- `4` is underlined in `nums2 = [1,3,4,2]`. There is no next greater element, so the answer is `-1`.
- `1` is underlined in `nums2 = [1,3,4,2]`. The next greater element is `3`.
- `2` is underlined in `nums2 = [1,3,4,2]`. There is no next greater element, so the answer is `-1`.
### Example 2:
- Input: `nums1 = [2,4], nums2 = [1,2,3,4]`
- Output: `[3,-1]`
- Explanation:
- The next greater element for each value of `nums1` is as follows:
- `2` is underlined in `nums2 = [1,2,3,4]`. The next greater element is `3`.
- `4` is underlined in `nums2 = [1,2,3,4]`. There is no next greater element, so the answer is `-1`.
### Constraints:
- `1 <= nums1.length <= nums2.length <= 1000`
- `0 <= nums1[i], nums2[i] <= 10^4`
- All integers in `nums1` and `nums2` are unique.
- All the integers of `nums1` also appear in nums2.
## Solution
```py
class Solution:
def nextGreaterElement(self, nums1: list[int], nums2: list[int]) -> list[int]:
mono_stack = []
next_greater = {}
for cur in nums2:
while mono_stack and cur > mono_stack[-1]:
val = mono_stack.pop()
next_greater[val] = cur
mono_stack.append(cur)
return [next_greater.get(x, -1) for x in nums1]
```
@@ -0,0 +1,45 @@
---
title: '901. Online Stock Span'
description: Design an algorithm that collects daily price quotes for some stock and returns the span of that stock's price for the current day
icon: dot
topics:
- { name: "Stack", slug: "stack" }
- { name: "Design", slug: "design" }
- { name: "Monotonic Stack", slug: "monotonic-stack" }
- { name: "Data Stream", slug: "data-stream" }
issue: "https://github.com/prdlk/leetcode/issues/75"
sidebar:
label: 'Online Stock Span'
badge: 'Medium'
---
### Example 1:
- Input: ``
- Output: ``
- Explanation: Input ["StockSpanner", "next", "next", "next", "next", "next", "next", "next"] `[[]`, `[100]`, `[80]`, `[60]`, `[70]`, `[60]`, `[75]`, [85]] Output [null, `1`, `1`, `1`, `2`, `1`, `4`, 6] Explanation StockSpanner stockSpanner = new StockSpanner(); stockSpanner.next(100); // return `1` stockSpanner.next(80); // return `1` stockSpanner.next(60); // return `1` stockSpanner.next(70); // return `2` stockSpanner.next(60); // return `1` stockSpanner.next(75); // return `4`, because the last `4` prices (including today's `price` of `75`) were less than or equal to today's `price`. stockSpanner.next(85); // return `6`
### Constraints:
- `1 <= price <= 10^5`
- At most 10^4 calls will be made to next.
## Solution
```py
class StockSpanner:
def __init__(self):
self.stack = []
def next(self, price: int) -> int:
ans = 1
while self.stack and self.stack[-1][0] <= price:
ans += self.stack.pop()[1]
self.stack.append([price, ans])
return ans
# Your StockSpanner object will be instantiated and called as such:
# obj = StockSpanner()
# param_1 = obj.next(price)
```
@@ -3,3 +3,34 @@ title: Monotonic Stack
description: 'A stack keeps its items in sorted order, which finds the next larger or smaller item in one pass.'
hidden: true
---
<YouTube id="Dq_ObZwTY_Q" title="Monotonic Stack" />
A monotonic stack keeps its values in sorted order as you push elements. The order can be increasing or decreasing, depending on the problem.
A decreasing monotonic stack is the most common version. Before pushing a new value `x`, we pop from the top while the top is smaller than or equal to `x`. After those pops, pushing `x` keeps the stack decreasing from bottom to top.
This push rule is the whole idea. A monotonic deque uses the same rule, but a deque also lets you remove old elements from the front, which is useful for sliding window problems.
## Example Implementation
This implementation maintains a decreasing monotonic stack.
```python
def mono_stack(insert_entries):
stack = []
for entry in insert_entries:
# Pop smaller (or equal) values so stack stays decreasing.
while stack and stack[-1] <= entry:
stack.pop()
# Do something with the popped item here
stack.append(entry)
```
:::tip
In interview problems, the stack usually stores indices instead of values. You can read the value from the original array when needed, and the index gives you distance information such as **"how many steps away is the next greater element."**
:::
For an array `nums`, if index `j` pops when you process index `i`, then `i` is the first index to the right that satisfies the stack condition for `j`. In a next-greater-element problem, that means `nums[i]` is the next greater value for `nums[j]`.
@@ -0,0 +1,46 @@
---
title: '76. Minimum Window Substring'
description: Given two strings s and t of lengths m and n respectively, return the minimum window substring of s such that every character in t (including duplicates) is included in the window. If there is no such substring, return the empty string ""
icon: dot
topics:
- { name: "Hash Table", slug: "hash-table" }
- { name: "String", slug: "string" }
- { name: "Sliding Window", slug: "sliding-window" }
issue: "https://github.com/prdlk/leetcode/issues/57"
sidebar:
label: 'Minimum Window Substring'
badge: 'Hard'
---
::::warning
Could you find an algorithm that runs in O(m + n) time?
::::
### Example 1:
- Input: `s = "ADOBECODEBANC", t = "ABC"`
- Output: `"BANC"`
- Explanation: The minimum window substring "BANC" includes 'A', 'B', and 'C' from string `t`.
### Example 2:
- Input: `s = "a", t = "a"`
- Output: `"a"`
- Explanation: The entire string `s` is the minimum window.
### Example 3:
- Input: `s = "a", t = "aa"`
- Output: `""`
- Explanation: Both 'a's from `t` must be included in the window. Since the largest window of `s` only has one 'a', return empty string.
### Constraints:
- `m == s.length`
- `n == t.length`
- `1 <= m, n <= 10^5`
- `s` and `t` consist of uppercase and lowercase English letters.
## Solution
```py
class Solution:
def minWindow(self, s: str, t: str) -> str:
```
@@ -0,0 +1,43 @@
---
title: '224. Basic Calculator'
description: Given a string s representing a valid expression, implement a basic calculator to evaluate it, and return the result of the evaluation
icon: dot
topics:
- { name: "Math", slug: "math" }
- { name: "String", slug: "string" }
- { name: "Stack", slug: "stack" }
- { name: "Recursion", slug: "recursion" }
issue: "https://github.com/prdlk/leetcode/issues/71"
sidebar:
label: 'Basic Calculator'
badge: 'Hard'
---
### Example 1:
- Input: `s = "1 + 1"`
- Output: `2`
### Example 2:
- Input: `s = " 2-1 + 2 "`
- Output: `3`
### Example 3:
- Input: `s = "(1+(4+5+2)-3)+(6+8)"`
- Output: `23`
### Constraints:
- `1 <= s.length <= 3 * 10^5`
- `s` consists of digits, '+', '-', '(', ')', and ' '.
- `s` represents a valid expression.
- '+' is not used as a unary operation (i.e., "+1" and "+(2 + 3)" is invalid).
- '-' could be used as a unary operation (i.e., "-1" and "-(2 + 3)" is valid).
- There will be no two consecutive operators in the input.
- Every number and running calculation will fit in a signed 32-bit integer.
## Solution
```py
class Solution:
def calculate(self, s: str) -> int:
```
@@ -3,3 +3,60 @@ title: Stack
description: 'Items are added and removed from one end, so the last item added is the first item removed.'
hidden: true
---
A stack works like a stack of plates in a cafeteria. You add new plates on top. When you need a plate, you take from the top. The last plate you added is the first one you remove.
This is **Last-In-First-Out (LIFO)**. The most recent item goes out first.
![](../../../public/stack-plates-metaphor.svg)
## Core Stack Operations
A stack has two fundamental operations:
- **Push**: add an item to the top of the stack
- **Pop**: remove the item at the top of the stack
Some stacks have additional operations:
- **Peek**: get the item at the top of the stack without removing it
- **IsEmpty**: check if the stack is empty
![](../../../public/stack-operations-sequence.svg)
```python
# Using Python list as a stack
stack = []
# Push elements
stack.append(10)
stack.append(20)
stack.append(30)
# Stack: [10, 20, 30] (30 is on top)
# Pop element
top = stack.pop() # Returns 30
# Stack: [10, 20]
# Peek at top
if stack:
top_element = stack[-1] # Returns 20 without removing
```
Python lists provide `append()` for pushing and `pop()` for popping. The `peek()` method is not built-in, but you can implement it yourself.
## Last in, First out
The defining characteristic of a stack is LIFO order. Whatever you put in last comes out first. This is different from a queue, which is First-In-First-Out (FIFO).
Add items: A, B, C
- Stack: [A, B, C] (C on top)
Remove items:
- First pop: C
- Second pop: B
- Third pop: A
The order reverses. Items come out in the opposite order they went in.
@@ -0,0 +1,41 @@
---
title: '80. Remove Duplicates from Sorted Array II'
description: Given an integer array nums sorted in non-decreasing order, remove some duplicates in-place such that each unique element appears at most twice. The relative order of the elements should be kept the same
icon: dot
topics:
- { name: "Array", slug: "array" }
- { name: "Two Pointers", slug: "two-pointers" }
issue: "https://github.com/prdlk/leetcode/issues/48"
sidebar:
label: 'Remove Duplicates from Sorted Array II'
badge: 'Medium'
---
### Example 1:
- Input: `nums = [1,1,1,2,2,3]`
- Output: `5, nums = [1,1,2,2,3,_]`
- Explanation: Your function should return `k = 5`, with the first five elements of `nums` being `1`, `1`, `2`, `2` and `3` respectively. It does not matter what you leave beyond the returned `k` (hence they are underscores).
### Example 2:
- Input: `nums = [0,0,1,1,1,1,2,3,3]`
- Output: `7, nums = [0,0,1,1,2,3,3,_,_]`
- Explanation: Your function should return `k = 7`, with the first seven elements of `nums` being `0`, `0`, `1`, `1`, `2`, `3` and `3` respectively. It does not matter what you leave beyond the returned `k` (hence they are underscores).
### Constraints:
- `1 <= nums.length <= 3 * 10^4`
- `-10^4 <= nums[i] <= 10^4`
- `nums` is sorted in non-decreasing order.
## Solution
```py
class Solution:
def removeDuplicates(self, nums: list[int]) -> int:
slow = 0
for fast in range(len(nums)):
if slow < 2 or nums[fast] != nums[slow - 2]:
nums[slow] = nums[fast]
slow += 1
return slow
```
@@ -3,3 +3,160 @@ title: Two Pointers
description: 'Two indices move through the data from different positions and meet at the answer.'
hidden: true
---
Most interview problems in this section fall into one of four families:
- **Same direction pointers**: one pointer reads, one pointer writes, or one pointer stays behind another while scanning left to right.
- **Opposite direction pointers**: one pointer starts on the left, one on the right, and each move eliminates impossible answers.
- **Sliding window**: the two pointers define a contiguous interval, and you maintain some property of that interval as the window expands and shrinks.
- **Fast and slow pointers**: both pointers move through a linked structure, but at different speeds or with a fixed gap.
![](../../../public/two-pointers-decision-tree.svg)
## Same Direction
Same-direction pointer problems usually scan from left to right, but the two pointers do different jobs.
Usually:
- one pointer reads the next value from the original input
- one pointer marks where the next kept value should go
Sometimes both pointers move forward together, but one pointer stays behind the other to preserve a gap or a relationship.
### When this family applies
Reach for same-direction pointers when the problem asks you to:
- rewrite an array in-place
- remove or compress elements without extra memory
- keep only the values that satisfy some condition
- maintain a fixed gap between two forward-moving pointers
### Core Invariant
The invariant in this family is usually about the prefix you have already processed.
Typical invariant:
> "Everything before the write pointer is already in the correct final form."
That is what makes these problems feel different from opposite-direction scans. You are not searching from both sides. You are building the answer progressively as you sweep through the input.
### Subtypes in this section
#### 1. Compaction / Overwrite
The slow pointer marks the next output position. The fast pointer scans the input. When the current value should be kept, copy it forward and advance the slow pointer.
![](../../../public/same-direction-compaction.svg)
#### 2. Relative Speed
Both pointers move forward, but one moves twice as fast. The invariant is about their distance and how much of the structure each pointer has consumed.
![](../../../public/same-direction-relative-speed.svg)
#### 3. Fixed Gap
Both pointers move forward while preserving a gap. The invariant is not about a rewritten prefix. It is about the distance between the pointers.
![](../../../public/same-direction-fixed-gap.svg)
### What changes across problems
The family stays the same, but the invariant changes slightly:
- in array compaction, the invariant is about the prefix being correct
- in fast/slow midpoint problems, the invariant is about relative speed
- in fixed-gap problems, the invariant is about distance
So the unifying idea is not "both pointers move right." The unifying idea is:
**"the structure to the left of the pointers already has a known meaning"**
### What to watch out for
Common mistakes:
- Updating the write pointer before copying the current value.
- Forgetting exactly what region is already valid.
- Losing track of whether the invariant is about a prefix, a gap, or relative speed.
As you go through the following problems, pay attention to one question:
**What does the region before the slow pointer mean right now?**
## Opposite Direction
Opposite-direction pointer problems place one pointer on the left and one on the right, then move inward.
The hard part is never the loop itself. **The hard part is justifying which pointer is safe to move.**
### When this family applies
Reach for opposite-direction pointers when the problem asks you to:
- the input is sorted, or can be made monotonic by sorting
- you are comparing a left choice and a right choice
- the problem has symmetry
- moving one side lets you eliminate a set of impossible answers
### Core Invariant
The invariant in this family is usually:
> "All candidate answers outside the current `[left, right]` range have already been ruled out."
That means each move must come with a proof. If you move a pointer without knowing why the discarded cases cannot contain the optimum, the algorithm is just guessing.
### Subtypes in this section
#### 1. Monotonic sum elimination
Because the array is sorted:
- if the sum is too small, moving the left pointer right is the only useful move
- if the sum is too large, moving the right pointer left is the only useful move
Sorted order gives you a monotonic rule.
![](../../../public/opposite-direction-monotonic-sum.svg)
#### 2. Symmetry checking
Here the pointers compare mirrored positions. The movement rule is mostly "move both," except when you must skip punctuation or spaces.
![](../../../public/opposite-direction-symmetry.svg)
#### 3. Bottleneck elimination
This one is important because it looks different from the other two. The key idea is that the shorter wall limits the area. If you keep that shorter wall and decrease the width, you cannot improve the answer. So it is safe to discard that wall.
![](../../../public/opposite-direction-bottleneck.svg)
### Why this is not a sliding window
Opposite-direction scans do not maintain an interval property like "this window is valid."
Instead, each step does one of these:
- rules out sums that are too small or too large
- checks mirrored characters
- discards a bottleneck boundary
The interval itself is just the remaining search space.
### What to watch out for
Common mistakes:
- Using two pointers on an unsorted pair problem without first creating monotonicity.
- Moving the taller wall in Container With Most Water without an elimination proof.
- Calling the method sliding window just because two boundaries exist.
As you read the problems in this family, keep asking:
**What exact set of candidate answers did this pointer move eliminate?**
-15
View File
@@ -1,15 +0,0 @@
---
title: Progress
description: Live campaign progress — solved by difficulty, recent attempts, the year by difficulty, topic coverage, and the most recently solved problems, fetched from the SRS Worker.
icon: loader
---
<div className="not-prose flex flex-col gap-6">
<SolvedRing />
<div className="grid gap-6 md:grid-cols-2">
<SubmissionHeatmap />
<SubmissionChart />
</div>
<TopicBubbles />
<RecentSolves />
</div>
+3 -3
View File
@@ -3,9 +3,9 @@
* browser so `blume build` never depends on the API being up. API_BASE is the
* deployed Worker — do not inline it anywhere else.
*
* Four islands read this (SolvedRing, SubmissionHeatmap, RecentSolves,
* TopicBubbles). Each hydrates on its own, but they share this module, so the
* one in-flight request is shared too: the page asks the Worker once.
* The five cards on pages/progress.astro read this (components/progress/).
* Each hydrates on its own, but they share this module, so the one in-flight
* request is shared too: the page asks the Worker once.
*
* Invariants: a malformed body is an error state, never a throw; the first
* frame is always the loading state, so server and client render identically.
+52
View File
@@ -0,0 +1,52 @@
---
/**
* `/progress`: the live campaign dashboard — five LeetCode-style React cards
* over GET /api/stats (lib/stats.ts), hydrated on load. A custom page like
* `/changelog` and `/appendix`, linked from the header's `navigation.tabs`,
* so it enters neither the sidebar tree nor prev/next. The cards live in
* components/progress/, not islands/: they are imported here, never used from
* MDX, so Blume has no reason to register them as global MDX components.
*/
import PageLayout from "blume/components/layout/PageLayout.astro";
import data from "blume:data";
import RecentSolves from "../components/progress/RecentSolves";
import SolvedRing from "../components/progress/SolvedRing";
import SubmissionChart from "../components/progress/SubmissionChart";
import SubmissionHeatmap from "../components/progress/SubmissionHeatmap";
import TopicBubbles from "../components/progress/TopicBubbles";
const { config } = data;
const description =
"Live campaign progress — solved by difficulty, recent attempts, the year by difficulty, topic coverage, and the most recently solved problems, fetched from the SRS Worker.";
---
<PageLayout
site={{ title: config.title, description: config.description }}
logo={config.logo}
favicon={config.favicon}
banner={config.banner}
analytics={config.analytics}
navigation={data.navigation}
fontCssVars={data.fontCssVars}
themeMode={config.theme.mode}
searchEnabled={config.search.enabled}
siteUrl={config.site}
ogEnabled={config.og.enabled}
structuredDataEnabled={config.structuredData}
page={{ title: `Progress - ${config.title}`, description, route: "/progress" }}
>
<section class="mx-auto w-full max-w-5xl px-6 py-16">
<h1 class="m-0 text-4xl font-semibold tracking-tight text-foreground">Progress</h1>
<p class="mt-3 max-w-3xl text-lg text-muted-foreground">{description}</p>
<div class="mt-10 flex flex-col gap-6">
<SolvedRing client:load />
<div class="grid gap-6 md:grid-cols-2">
<SubmissionHeatmap client:load />
<SubmissionChart client:load />
</div>
<TopicBubbles client:load />
<RecentSolves client:load />
</div>
</section>
</PageLayout>
@@ -0,0 +1,41 @@
<svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg">
<!-- Background (REQUIRED) -->
<rect width="800" height="450" fill="#f8f9fa" rx="10"/>
<!-- Title and Subtitle -->
<text x="400" y="45" font-family="Arial, sans-serif" font-size="26" font-weight="bold" fill="#2c3e50" text-anchor="middle">Opposite Direction: Bottleneck Elimination</text>
<text x="400" y="75" font-family="Arial, sans-serif" font-size="16" fill="#7f8c8d" text-anchor="middle">Container With Most Water: discard the shorter wall.</text>
<!-- Container -->
<g transform="translate(150, 130)">
<!-- Walls -->
<rect x="0" y="20" width="20" height="150" fill="#3498db" stroke="#2c3e50" stroke-width="2"/>
<text x="10" y="10" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#2c3e50" text-anchor="middle">8</text>
<polygon points="10,180 5,190 15,190" fill="#3498db"/>
<text x="10" y="205" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">L</text>
<rect x="50" y="100" width="20" height="70" fill="#94a3b8" stroke="#2c3e50" stroke-width="1"/>
<rect x="100" y="130" width="20" height="40" fill="#94a3b8" stroke="#2c3e50" stroke-width="1"/>
<rect x="150" y="40" width="20" height="130" fill="#94a3b8" stroke="#2c3e50" stroke-width="1"/>
<rect x="200" y="90" width="20" height="80" fill="#94a3b8" stroke="#2c3e50" stroke-width="1"/>
<rect x="250" y="20" width="20" height="150" fill="#94a3b8" stroke="#2c3e50" stroke-width="1"/>
<rect x="300" y="60" width="20" height="110" fill="#94a3b8" stroke="#2c3e50" stroke-width="1"/>
<rect x="350" y="110" width="20" height="60" fill="#ef4444" stroke="#2c3e50" stroke-width="2"/>
<text x="360" y="100" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#2c3e50" text-anchor="middle">3</text>
<polygon points="360,180 355,190 365,190" fill="#ef4444"/>
<text x="360" y="205" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#ef4444" text-anchor="middle">R</text>
<!-- Water -->
<rect x="20" y="110" width="330" height="60" fill="#3498db" fill-opacity="0.2"/>
<!-- Rule -->
<g transform="translate(0, 240)">
<text x="185" y="0" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="#2c3e50" text-anchor="middle">The bottleneck rule:</text>
<text x="185" y="25" font-family="Arial, sans-serif" font-size="14" fill="#7f8c8d" text-anchor="middle">Since height[R] &lt; height[L], the only way to find</text>
<text x="185" y="45" font-family="Arial, sans-serif" font-size="14" fill="#7f8c8d" text-anchor="middle">a larger area is to discard R and move left.</text>
<path d="M 370,110 Q 400,110 400,240" fill="none" stroke="#ef4444" stroke-width="2" stroke-dasharray="5,5"/>
<polygon points="400,240 395,230 405,230" fill="#ef4444"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,76 @@
<svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg">
<!-- Background (REQUIRED) -->
<rect width="800" height="450" fill="#f8f9fa" rx="10"/>
<!-- Title and Subtitle -->
<text x="400" y="45" font-family="Arial, sans-serif" font-size="26" font-weight="bold" fill="#2c3e50" text-anchor="middle">Opposite Direction: Monotonic Sum</text>
<text x="400" y="75" font-family="Arial, sans-serif" font-size="16" fill="#7f8c8d" text-anchor="middle">Two Sum Sorted: target = 16. Adjust pointers based on sum.</text>
<!-- State 1: Sum too large -->
<g transform="translate(100, 130)">
<text x="0" y="-20" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">1. Sum is too large (2 + 15 = 17)</text>
<!-- Array -->
<g transform="translate(50, 20)">
<!-- Left Pointer (Blue) -->
<rect x="0" y="0" width="60" height="50" fill="#e3f2fd" stroke="#3498db" stroke-width="3" rx="3"/>
<text x="30" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1565c0">2</text>
<polygon points="30,60 25,70 35,70" fill="#3498db"/>
<text x="30" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">L</text>
<rect x="70" y="0" width="60" height="50" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="3"/>
<text x="100" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">5</text>
<rect x="140" y="0" width="60" height="50" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="3"/>
<text x="170" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">8</text>
<rect x="210" y="0" width="60" height="50" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="3"/>
<text x="240" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">11</text>
<!-- Right Pointer (Orange) -->
<rect x="280" y="0" width="60" height="50" fill="#fff7ed" stroke="#f39c12" stroke-width="3" rx="3"/>
<text x="310" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#8a5a00">15</text>
<polygon points="310,60 305,70 315,70" fill="#f39c12"/>
<text x="310" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#8a5a00" text-anchor="middle">R</text>
<!-- Elimination Note -->
<text x="400" y="32" font-family="Arial, sans-serif" font-size="14" fill="#ef4444">17 &gt; 16: R must move left.</text>
</g>
</g>
<!-- Transition -->
<g transform="translate(400, 260)">
<path d="M 0,0 L 0,30" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="10" y="20" font-family="Arial, sans-serif" font-size="14" font-style="italic" fill="#7f8c8d">Move Right pointer left</text>
</g>
<!-- State 2: Sum too small -->
<g transform="translate(100, 340)">
<text x="0" y="-20" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">2. Sum is too small (2 + 11 = 13)</text>
<!-- Array -->
<g transform="translate(50, 20)">
<rect x="0" y="0" width="60" height="50" fill="#e3f2fd" stroke="#3498db" stroke-width="3" rx="3"/>
<text x="30" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1565c0">2</text>
<polygon points="30,60 25,70 35,70" fill="#3498db"/>
<text x="30" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">L</text>
<rect x="70" y="0" width="60" height="50" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="3"/>
<text x="100" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">5</text>
<rect x="140" y="0" width="60" height="50" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="3"/>
<text x="170" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">8</text>
<rect x="210" y="0" width="60" height="50" fill="#fff7ed" stroke="#f39c12" stroke-width="3" rx="3"/>
<text x="240" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#8a5a00">11</text>
<polygon points="240,60 235,70 245,70" fill="#f39c12"/>
<text x="240" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#8a5a00" text-anchor="middle">R</text>
<rect x="280" y="0" width="60" height="50" fill="#e5e7eb" stroke="#94a3b8" stroke-width="2" rx="3"/>
<text x="310" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#94a3b8">15</text>
<!-- Elimination Note -->
<text x="400" y="32" font-family="Arial, sans-serif" font-size="14" fill="#3498db">13 &lt; 16: L must move right.</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

@@ -0,0 +1,48 @@
<svg viewBox="0 0 800 350" xmlns="http://www.w3.org/2000/svg">
<!-- Background (REQUIRED) -->
<rect width="800" height="350" fill="#f8f9fa" rx="10"/>
<!-- Title and Subtitle -->
<text x="400" y="45" font-family="Arial, sans-serif" font-size="26" font-weight="bold" fill="#2c3e50" text-anchor="middle">Opposite Direction: Symmetry Checking</text>
<text x="400" y="75" font-family="Arial, sans-serif" font-size="16" fill="#7f8c8d" text-anchor="middle">Valid Palindrome: Comparing mirrored positions from outside in.</text>
<!-- Array -->
<g transform="translate(150, 150)">
<!-- Elements -->
<rect x="0" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="3"/>
<text x="30" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">r</text>
<rect x="70" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="3"/>
<text x="100" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">a</text>
<rect x="140" y="0" width="60" height="50" fill="#fef3c7" stroke="#f59e0b" stroke-width="3" rx="3"/>
<text x="170" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">c</text>
<polygon points="170,-10 165,-20 175,-20" fill="#f59e0b"/>
<text x="170" y="-30" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#f59e0b" text-anchor="middle">CHECK</text>
<rect x="210" y="0" width="60" height="50" fill="#fef3c7" stroke="#f59e0b" stroke-width="3" rx="3"/>
<text x="240" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">e</text>
<polygon points="240,-10 235,-20 245,-20" fill="#f59e0b"/>
<text x="240" y="-30" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#f59e0b" text-anchor="middle">CHECK</text>
<rect x="280" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="3"/>
<text x="310" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">a</text>
<rect x="350" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="3"/>
<text x="380" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">r</text>
<!-- Pointers -->
<polygon points="170,60 165,70 175,70" fill="#3498db"/>
<text x="170" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">L</text>
<polygon points="240,60 235,70 245,70" fill="#ef4444"/>
<text x="240" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#ef4444" text-anchor="middle">R</text>
<!-- Correct markings -->
<path d="M 30,100 L 30,110 L 380,110 L 380,100" fill="none" stroke="#10b981" stroke-width="2"/>
<text x="205" y="130" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#10b981" text-anchor="middle">OUTER SYMMETRY VALIDATED</text>
</g>
<!-- Observation -->
<text x="400" y="320" font-family="Arial, sans-serif" font-size="14" font-style="italic" fill="#7f8c8d" text-anchor="middle">Oops! 'c' != 'e', not a palindrome.</text>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,80 @@
<svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg">
<!-- Background (REQUIRED) -->
<rect width="800" height="450" fill="#f8f9fa" rx="10"/>
<!-- Title and Subtitle -->
<text x="400" y="45" font-family="Arial, sans-serif" font-size="26" font-weight="bold" fill="#2c3e50" text-anchor="middle">Same-Direction Pointers: Compaction</text>
<text x="400" y="75" font-family="Arial, sans-serif" font-size="16" fill="#7f8c8d" text-anchor="middle">Building a correct prefix while scanning the input.</text>
<!-- Before Section -->
<g transform="translate(150, 130)">
<text x="0" y="-20" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">1. Before copying</text>
<!-- Array -->
<g transform="translate(0, 20)">
<!-- Correct Prefix (Green) -->
<rect x="0" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="3"/>
<text x="30" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">1</text>
<rect x="70" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="3"/>
<text x="100" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">2</text>
<!-- Write Position (Yellow) -->
<rect x="140" y="0" width="60" height="50" fill="#fef3c7" stroke="#f59e0b" stroke-width="3" rx="3"/>
<text x="170" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#8a5a00">0</text>
<polygon points="170,60 165,70 175,70" fill="#f59e0b"/>
<text x="170" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#8a5a00" text-anchor="middle">write</text>
<!-- Scanning (Blue) -->
<rect x="210" y="0" width="60" height="50" fill="#e3f2fd" stroke="#3498db" stroke-width="3" rx="3"/>
<text x="240" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1565c0">4</text>
<polygon points="240,60 235,70 245,70" fill="#3498db"/>
<text x="240" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1565c0" text-anchor="middle">read</text>
<rect x="280" y="0" width="60" height="50" fill="#ffffff" stroke="#dee2e6" stroke-width="2" rx="3"/>
<text x="310" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#2c3e50">3</text>
<!-- Prefix label -->
<path d="M 0,-10 L 0,-20 L 130,-20 L 130,-10" fill="none" stroke="#10b981" stroke-width="2"/>
<text x="65" y="-30" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#10b981" text-anchor="middle">CORRECT PREFIX</text>
</g>
</g>
<!-- Transition Label -->
<g transform="translate(400, 270)">
<path d="M 0,0 L 0,30" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="10" y="20" font-family="Arial, sans-serif" font-size="14" font-style="italic" fill="#7f8c8d">Copy read value to write position</text>
</g>
<!-- After Section -->
<g transform="translate(150, 340)">
<text x="0" y="-20" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">2. After copying and advancing</text>
<!-- Array -->
<g transform="translate(0, 20)">
<rect x="0" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="3"/>
<text x="30" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">1</text>
<rect x="70" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="3"/>
<text x="100" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">2</text>
<rect x="140" y="0" width="60" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="3"/>
<text x="170" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1b5e20">4</text>
<!-- New Write Position -->
<rect x="210" y="0" width="60" height="50" fill="#fef3c7" stroke="#f59e0b" stroke-width="3" rx="3"/>
<text x="240" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#8a5a00">4</text>
<polygon points="240,60 235,70 245,70" fill="#f59e0b"/>
<text x="240" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#8a5a00" text-anchor="middle">write</text>
<!-- New Scanning Position -->
<rect x="280" y="0" width="60" height="50" fill="#e3f2fd" stroke="#3498db" stroke-width="3" rx="3"/>
<text x="310" y="32" font-family="monospace" font-size="22" text-anchor="middle" fill="#1565c0">3</text>
<polygon points="310,60 305,70 315,70" fill="#3498db"/>
<text x="310" y="85" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#1565c0" text-anchor="middle">read</text>
<path d="M 0,-10 L 0,-20 L 200,-20 L 200,-10" fill="none" stroke="#10b981" stroke-width="2"/>
<text x="100" y="-30" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#10b981" text-anchor="middle">PREFIX GREW</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

@@ -0,0 +1,97 @@
<svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg">
<!-- Background (REQUIRED) -->
<rect width="800" height="450" fill="#f8f9fa" rx="10"/>
<!-- Title and Subtitle -->
<text x="400" y="45" font-family="Arial, sans-serif" font-size="26" font-weight="bold" fill="#2c3e50" text-anchor="middle">Same-Direction: Fixed Gap</text>
<text x="400" y="75" font-family="Arial, sans-serif" font-size="16" fill="#7f8c8d" text-anchor="middle">Maintaining a distance of N while traversing.</text>
<!-- State 1 -->
<g transform="translate(100, 150)">
<text x="0" y="-30" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">1. Gap established (N=2)</text>
<!-- List -->
<g transform="translate(50, 20)">
<!-- Pointers -->
<polygon points="0,5 -5,-5 5,-5" fill="#3498db"/>
<text x="0" y="-10" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">left</text>
<polygon points="160,5 155,-5 165,-5" fill="#f39c12"/>
<text x="160" y="-10" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#f39c12" text-anchor="middle">right</text>
<!-- Nodes -->
<circle cx="0" cy="25" r="22" fill="#e3f2fd" stroke="#3498db" stroke-width="3"/>
<text x="0" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">1</text>
<circle cx="80" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="80" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">2</text>
<circle cx="160" cy="25" r="22" fill="#fff7ed" stroke="#f39c12" stroke-width="3"/>
<text x="160" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">3</text>
<circle cx="240" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="240" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">4</text>
<circle cx="320" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="320" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">5</text>
<!-- Connectors -->
<line x1="22" y1="25" x2="58" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="102" y1="25" x2="138" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="182" y1="25" x2="218" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="262" y1="25" x2="298" y2="25" stroke="#94a3b8" stroke-width="2"/>
<!-- Gap marker -->
<path d="M 0,60 L 0,70 L 160,70 L 160,60" fill="none" stroke="#7f8c8d" stroke-width="2"/>
<text x="80" y="85" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#7f8c8d" text-anchor="middle">GAP OF 2</text>
</g>
</g>
<!-- Transition -->
<g transform="translate(400, 260)">
<path d="M 0,0 L 0,30" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="10" y="20" font-family="Arial, sans-serif" font-size="14" font-style="italic" fill="#7f8c8d">Move both together</text>
</g>
<!-- State 2 -->
<g transform="translate(100, 340)">
<text x="0" y="-30" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">2. Right hits the end</text>
<!-- List -->
<g transform="translate(50, 20)">
<!-- Pointers -->
<polygon points="160,5 155,-5 165,-5" fill="#3498db"/>
<text x="160" y="-10" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">left</text>
<polygon points="320,5 315,-5 325,-5" fill="#f39c12"/>
<text x="320" y="-10" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#f39c12" text-anchor="middle">right</text>
<!-- Nodes -->
<circle cx="0" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="0" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">1</text>
<circle cx="80" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="80" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">2</text>
<circle cx="160" cy="25" r="22" fill="#e3f2fd" stroke="#3498db" stroke-width="3"/>
<text x="160" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">3</text>
<circle cx="240" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="240" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">4</text>
<circle cx="320" cy="25" r="22" fill="#fff7ed" stroke="#f39c12" stroke-width="3"/>
<text x="320" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">5</text>
<!-- Connectors -->
<line x1="22" y1="25" x2="58" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="102" y1="25" x2="138" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="182" y1="25" x2="218" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="262" y1="25" x2="298" y2="25" stroke="#94a3b8" stroke-width="2"/>
<!-- Gap marker -->
<path d="M 160,60 L 160,70 L 320,70 L 320,60" fill="none" stroke="#7f8c8d" stroke-width="2"/>
<text x="240" y="85" font-family="Arial, sans-serif" font-size="12" font-weight="bold" fill="#7f8c8d" text-anchor="middle">STILL GAP OF 2</text>
</g>
<text x="400" y="45" font-family="Arial, sans-serif" font-size="14" fill="#2c3e50">Left is now N steps from the end.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

@@ -0,0 +1,89 @@
<svg viewBox="0 0 800 450" xmlns="http://www.w3.org/2000/svg">
<!-- Background (REQUIRED) -->
<rect width="800" height="450" fill="#f8f9fa" rx="10"/>
<!-- Title and Subtitle -->
<text x="400" y="45" font-family="Arial, sans-serif" font-size="26" font-weight="bold" fill="#2c3e50" text-anchor="middle">Same-Direction: Relative Speed</text>
<text x="400" y="75" font-family="Arial, sans-serif" font-size="16" fill="#7f8c8d" text-anchor="middle">Fast pointer moves at 2x speed to find the midpoint.</text>
<!-- State 1 -->
<g transform="translate(100, 150)">
<text x="0" y="-30" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">1. After one move</text>
<!-- List -->
<g transform="translate(50, 20)">
<!-- Pointers -->
<polygon points="80,5 75,-5 85,-5" fill="#3498db"/>
<text x="80" y="-10" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">slow</text>
<polygon points="160,5 155,-5 165,-5" fill="#ef4444"/>
<text x="160" y="-10" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#ef4444" text-anchor="middle">fast</text>
<!-- Nodes -->
<circle cx="0" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="0" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">1</text>
<circle cx="80" cy="25" r="22" fill="#e3f2fd" stroke="#3498db" stroke-width="3"/>
<text x="80" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">2</text>
<circle cx="160" cy="25" r="22" fill="#fee2e2" stroke="#ef4444" stroke-width="3"/>
<text x="160" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">3</text>
<circle cx="240" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="240" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">4</text>
<circle cx="320" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="320" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">5</text>
<!-- Connectors -->
<line x1="22" y1="25" x2="58" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="102" y1="25" x2="138" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="182" y1="25" x2="218" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="262" y1="25" x2="298" y2="25" stroke="#94a3b8" stroke-width="2"/>
</g>
</g>
<!-- Transition -->
<g transform="translate(400, 260)">
<path d="M 0,0 L 0,30" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="10" y="20" font-family="Arial, sans-serif" font-size="14" font-style="italic" fill="#7f8c8d">Move: slow +1, fast +2</text>
</g>
<!-- State 2 -->
<g transform="translate(100, 340)">
<text x="0" y="-30" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">2. Reached the middle</text>
<!-- List -->
<g transform="translate(50, 20)">
<!-- Pointers -->
<polygon points="160,5 155,-5 165,-5" fill="#3498db"/>
<text x="160" y="-10" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db" text-anchor="middle">slow</text>
<polygon points="320,5 315,-5 325,-5" fill="#ef4444"/>
<text x="320" y="-10" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#ef4444" text-anchor="middle">fast</text>
<!-- Nodes -->
<circle cx="0" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="0" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">1</text>
<circle cx="80" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="80" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">2</text>
<circle cx="160" cy="25" r="22" fill="#e3f2fd" stroke="#3498db" stroke-width="3"/>
<text x="160" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">3</text>
<circle cx="240" cy="25" r="22" fill="#ffffff" stroke="#94a3b8" stroke-width="2"/>
<text x="240" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">4</text>
<circle cx="320" cy="25" r="22" fill="#fee2e2" stroke="#ef4444" stroke-width="3"/>
<text x="320" y="31" font-family="monospace" font-size="18" text-anchor="middle" fill="#2c3e50">5</text>
<!-- Connectors -->
<line x1="22" y1="25" x2="58" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="102" y1="25" x2="138" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="182" y1="25" x2="218" y2="25" stroke="#94a3b8" stroke-width="2"/>
<line x1="262" y1="25" x2="298" y2="25" stroke="#94a3b8" stroke-width="2"/>
</g>
<text x="400" y="45" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3498db">Midpoint found!</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Pixodesk SVG (https://pixodesk.com) -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 400" text-rendering="geometricPrecision" shape-rendering="geometricPrecision">
<defs>
<marker id="arrowhead" refX="9" refY="3" markerHeight="10" markerWidth="10" orient="auto">
<path fill="#e74c3c" d="M0,0L10,3L0,6"/>
</marker>
</defs>
<rect fill="#f8f9fa" rx="10" ry="0" width="900" height="400"/>
<text fill="#2c3e50" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="700" xml:space="preserve" x="450" y="30" data-gn-el-type="text" style="white-space:pre"><tspan>Core Stack Operations</tspan></text>
<g id="push-operation">
<text fill="#27ae60" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" font-weight="700" xml:space="preserve" x="90" y="75" data-gn-el-type="text" style="white-space:pre"><tspan>PUSH</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="12" xml:space="preserve" x="50" y="95" data-gn-el-type="text" style="white-space:pre"><tspan>Add to top</tspan></text>
<rect fill="#ffffff" stroke="#bdc3c7" stroke-width="2" rx="5" ry="0" x="40" y="110" width="100" height="200"/>
<rect fill="#3498db" stroke="#2980b9" stroke-width="2" rx="3" ry="0" x="50" y="280" width="80" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="90" y="297" data-gn-el-type="text" style="white-space:pre"><tspan>10</tspan></text>
<rect fill="#3498db" stroke="#2980b9" stroke-width="2" rx="3" ry="0" x="50" y="250" width="80" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="90" y="267" data-gn-el-type="text" style="white-space:pre"><tspan>20</tspan></text>
<rect fill="#27ae60" stroke="#229954" stroke-width="2" rx="3" ry="0" x="50" y="200" width="80" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="90" y="217" data-gn-el-type="text" style="white-space:pre"><tspan>30</tspan></text>
<path fill="none" stroke="#27ae60" stroke-width="2" marker-end="url(#arrowhead)" d="M30,180L30,210"/>
<text fill="#27ae60" font-family="Arial, sans-serif" font-size="11" font-weight="700" xml:space="preserve" x="10" y="170" data-gn-el-type="text" style="white-space:pre"><tspan>New</tspan></text>
<text fill="#e74c3c" font-family="Arial, sans-serif" font-size="11" font-weight="700" xml:space="preserve" x="140" y="215" data-gn-el-type="text" style="white-space:pre"><tspan>← TOP</tspan></text>
</g>
<g id="pop-operation">
<text fill="#e74c3c" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" font-weight="700" xml:space="preserve" x="270" y="75" data-gn-el-type="text" style="white-space:pre"><tspan>POP</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="12" xml:space="preserve" x="215" y="95" data-gn-el-type="text" style="white-space:pre"><tspan>Remove from top</tspan></text>
<rect fill="#ffffff" stroke="#bdc3c7" stroke-width="2" rx="5" ry="0" x="220" y="110" width="100" height="200"/>
<rect fill="#3498db" stroke="#2980b9" stroke-width="2" rx="3" ry="0" x="230" y="280" width="80" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="270" y="297" data-gn-el-type="text" style="white-space:pre"><tspan>10</tspan></text>
<rect fill="#3498db" stroke="#2980b9" stroke-width="2" rx="3" ry="0" x="230" y="250" width="80" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="270" y="267" data-gn-el-type="text" style="white-space:pre"><tspan>20</tspan></text>
<rect fill="#e74c3c" stroke="#c0392b" stroke-width="2" opacity="0.5" rx="3" ry="0" x="230" y="180" width="80" height="25"/>
<text fill="#ffffff" opacity="0.5" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="270" y="197" data-gn-el-type="text" style="white-space:pre"><tspan>30</tspan></text>
<path fill="none" stroke="#e74c3c" stroke-width="2" marker-end="url(#arrowhead)" d="M330,185L360,150"/>
<text fill="#e74c3c" font-family="Arial, sans-serif" font-size="11" font-weight="700" xml:space="preserve" transform="translate(-3, 7)" x="365" y="145" data-gn-el-type="text" style="white-space:pre"><tspan>Removed</tspan></text>
<rect fill="#e74c3c" stroke="#c0392b" stroke-width="2" rx="3" ry="0" x="350" y="115" width="60" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="380" y="132" data-gn-el-type="text" style="white-space:pre"><tspan>30</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="10" xml:space="preserve" x="350" y="108" data-gn-el-type="text" style="white-space:pre"><tspan>Returns:</tspan></text>
<text fill="#e74c3c" font-family="Arial, sans-serif" font-size="11" font-weight="700" xml:space="preserve" x="320" y="265" data-gn-el-type="text" style="white-space:pre"><tspan>← TOP</tspan></text>
</g>
<g id="peek-operation">
<text fill="#f39c12" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" font-weight="700" xml:space="preserve" x="520" y="75" data-gn-el-type="text" style="white-space:pre"><tspan>PEEK</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="12" xml:space="preserve" x="465" y="95" data-gn-el-type="text" style="white-space:pre"><tspan>View top (no remove)</tspan></text>
<rect fill="#ffffff" stroke="#bdc3c7" stroke-width="2" rx="5" ry="0" x="470" y="110" width="100" height="200"/>
<rect fill="#3498db" stroke="#2980b9" stroke-width="2" rx="3" ry="0" x="480" y="280" width="80" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="520" y="297" data-gn-el-type="text" style="white-space:pre"><tspan>10</tspan></text>
<rect fill="#3498db" stroke="#2980b9" stroke-width="2" rx="3" ry="0" x="480" y="250" width="80" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="520" y="267" data-gn-el-type="text" style="white-space:pre"><tspan>20</tspan></text>
<rect fill="#f39c12" stroke="#e67e22" stroke-width="3" rx="3" ry="0" x="480" y="220" width="80" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="520" y="237" data-gn-el-type="text" style="white-space:pre"><tspan>30</tspan></text>
<path fill="none" stroke="#f39c12" stroke-width="2" stroke-dasharray="4 4" d="M580,232L620,190"/>
<text fill="#f39c12" font-family="Arial, sans-serif" font-size="11" font-weight="700" xml:space="preserve" transform="translate(-2, 12)" x="625" y="185" data-gn-el-type="text" style="white-space:pre"><tspan>View only</tspan></text>
<rect fill="#f39c12" stroke="#e67e22" stroke-width="2" rx="3" ry="0" x="610" y="160" width="60" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" x="640" y="177" data-gn-el-type="text" style="white-space:pre"><tspan>30</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="10" xml:space="preserve" x="610" y="153" data-gn-el-type="text" style="white-space:pre"><tspan>Returns:</tspan></text>
<text fill="#e74c3c" font-family="Arial, sans-serif" font-size="11" font-weight="700" xml:space="preserve" x="570" y="235" data-gn-el-type="text" style="white-space:pre"><tspan>← TOP</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="10" xml:space="preserve" transform="translate(8, 0)" x="470" y="190" data-gn-el-type="text" style="white-space:pre"><tspan>Stack unchanged</tspan></text>
</g>
<g id="isEmpty-operation">
<text fill="#9b59b6" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" font-weight="700" xml:space="preserve" x="780" y="75" data-gn-el-type="text" style="white-space:pre"><tspan>isEmpty</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="12" xml:space="preserve" x="730" y="95" data-gn-el-type="text" style="white-space:pre"><tspan>Check if empty</tspan></text>
<rect fill="#ffffff" stroke="#bdc3c7" stroke-width="2" rx="5" ry="0" x="730" y="110" width="100" height="200"/>
<text fill="#bdc3c7" text-anchor="middle" font-family="Arial, sans-serif" font-style="italic" font-size="14" xml:space="preserve" x="780" y="215" data-gn-el-type="text" style="white-space:pre"><tspan>Empty</tspan></text>
<rect fill="#9b59b6" stroke="#8e44ad" stroke-width="2" transform="translate(84.1444, -90.4029)" rx="3" ry="0" x="750" y="250" width="60" height="25"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" font-weight="700" xml:space="preserve" transform="translate(84.1444, -90.4029)" x="780" y="267" data-gn-el-type="text" style="white-space:pre"><tspan>true</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="10" xml:space="preserve" transform="translate(86.9966, -89.3466)" x="750" y="243" data-gn-el-type="text" style="white-space:pre"><tspan>Returns:</tspan></text>
</g>
<g id="explanation">
<rect fill="#ffffff" stroke="#bdc3c7" stroke-width="2" rx="8" ry="0" x="40" y="330" width="820" height="55"/>
<text fill="#2c3e50" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="450" y="350" data-gn-el-type="text" style="white-space:pre"><tspan>All operations work on the TOP of the stack</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="12" xml:space="preserve" transform="translate(-47, 0)" x="100" y="370" data-gn-el-type="text" style="white-space:pre"><tspan>• PUSH adds element to top</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="12" xml:space="preserve" transform="translate(-47, 0)" x="300" y="370" data-gn-el-type="text" style="white-space:pre"><tspan>• POP removes and returns top</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="12" xml:space="preserve" transform="translate(-47, 0)" x="520" y="370" data-gn-el-type="text" style="white-space:pre"><tspan>• PEEK returns top without removing</tspan></text>
<text fill="#7f8c8d" font-family="Arial, sans-serif" font-size="12" xml:space="preserve" transform="translate(-47, 0)" x="760" y="370" data-gn-el-type="text" style="white-space:pre"><tspan>• isEmpty checks size</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Pixodesk SVG (https://pixodesk.com) -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400" text-rendering="geometricPrecision" shape-rendering="geometricPrecision">
<defs>
<marker id="arrowhead" refX="9" refY="3" markerHeight="10" markerWidth="10" orient="auto">
<path fill="#e74c3c" d="M0,0L10,3L0,6"/>
</marker>
<marker id="arrowhead-green" refX="9" refY="3" markerHeight="10" markerWidth="10" orient="auto">
<path fill="#27ae60" d="M0,0L10,3L0,6"/>
</marker>
</defs>
<rect fill="#f8f9fa" rx="10" ry="0" width="800" height="399.6807"/>
<text fill="#2c3e50" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" font-weight="700" xml:space="preserve" x="400" y="35" data-gn-el-type="text" style="white-space:pre"><tspan>Stack of Plates - LIFO (Last In, First Out)</tspan></text>
<g id="push-operation" transform="translate(0, -105)">
<text fill="#27ae60" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="700" xml:space="preserve" transform="translate(0, 102)" x="200" y="80" data-gn-el-type="text" style="white-space:pre"><tspan>Adding Plates (Push)</tspan></text>
<rect fill="#7f8c8d" rx="3" ry="0" x="120" y="380" width="160" height="15" style="display:none"/>
<ellipse fill="#3498db" stroke="#2980b9" stroke-width="2" cx="200" cy="370" rx="70" ry="12"/>
<rect fill="#3498db" x="130" y="370" width="140" height="25"/>
<ellipse fill="#2980b9" stroke="#2471a3" stroke-width="2" cx="200" cy="395" rx="70" ry="12"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="200" y="387" data-gn-el-type="text" style="white-space:pre"><tspan>Plate 1</tspan></text>
<ellipse fill="#3498db" stroke="#2980b9" stroke-width="2" cx="200" cy="330" rx="70" ry="12"/>
<rect fill="#3498db" x="130" y="330" width="140" height="25"/>
<ellipse fill="#2980b9" stroke="#2471a3" stroke-width="2" cx="200" cy="355" rx="70" ry="12"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="200" y="347" data-gn-el-type="text" style="white-space:pre"><tspan>Plate 2</tspan></text>
<ellipse fill="#27ae60" stroke="#229954" stroke-width="2" cx="200" cy="290" rx="70" ry="12"/>
<rect fill="#27ae60" x="130" y="290" width="140" height="25"/>
<ellipse fill="#229954" stroke="#1e8449" stroke-width="2" cx="200" cy="315" rx="70" ry="12"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="200" y="307" data-gn-el-type="text" style="white-space:pre"><tspan>Plate 3</tspan></text>
<path fill="none" stroke="#27ae60" stroke-width="3" marker-end="url(#arrowhead-green)" d="M200,250L200,275"/>
<text fill="#27ae60" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="230" y="265" data-gn-el-type="text" style="white-space:pre"><tspan>Add on top</tspan></text>
<text fill="#e74c3c" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="85" y="307" data-gn-el-type="text" style="white-space:pre"><tspan>TOP ➜</tspan></text>
</g>
<g id="lifo-flow" transform="translate(0, -105)">
<path fill="none" stroke="#95a5a6" stroke-width="3" marker-end="url(#arrowhead)" d="M320,300L480,300"/>
<text fill="#e74c3c" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" font-weight="700" xml:space="preserve" x="400" y="290" data-gn-el-type="text" style="white-space:pre"><tspan>LIFO</tspan></text>
<text fill="#7f8c8d" text-anchor="middle" font-family="Arial, sans-serif" font-size="13" xml:space="preserve" x="400" y="330" data-gn-el-type="text" style="white-space:pre"><tspan>Last plate added</tspan></text>
<text fill="#7f8c8d" text-anchor="middle" font-family="Arial, sans-serif" font-size="13" xml:space="preserve" x="400" y="348" data-gn-el-type="text" style="white-space:pre"><tspan>is first removed</tspan></text>
</g>
<g id="pop-operation" transform="translate(0, -105)">
<text fill="#e74c3c" text-anchor="middle" font-family="Arial, sans-serif" font-size="18" font-weight="700" xml:space="preserve" transform="translate(0, 101)" x="600" y="80" data-gn-el-type="text" style="white-space:pre"><tspan>Removing Plates (Pop)</tspan></text>
<ellipse fill="#3498db" stroke="#2980b9" stroke-width="2" cx="600" cy="370" rx="70" ry="12"/>
<rect fill="#3498db" x="530" y="370" width="140" height="25"/>
<ellipse fill="#2980b9" stroke="#2471a3" stroke-width="2" cx="600" cy="395" rx="70" ry="12"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="600" y="387" data-gn-el-type="text" style="white-space:pre"><tspan>Plate 1</tspan></text>
<ellipse fill="#3498db" stroke="#2980b9" stroke-width="2" cx="600" cy="330" rx="70" ry="12"/>
<rect fill="#3498db" x="530" y="330" width="140" height="25"/>
<ellipse fill="#2980b9" stroke="#2471a3" stroke-width="2" cx="600" cy="355" rx="70" ry="12"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="600" y="347" data-gn-el-type="text" style="white-space:pre"><tspan>Plate 2</tspan></text>
<ellipse fill="#e74c3c" stroke="#c0392b" stroke-width="2" cx="600" cy="230" rx="70" ry="12"/>
<rect fill="#e74c3c" x="530" y="230" width="140" height="25"/>
<ellipse fill="#c0392b" stroke="#a93226" stroke-width="2" cx="600" cy="255" rx="70" ry="12"/>
<text fill="#ffffff" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="600" y="247" data-gn-el-type="text" style="white-space:pre"><tspan>Plate 3</tspan></text>
<path fill="none" stroke="#e74c3c" stroke-width="3" transform="translate(0, 63)" marker-end="url(#arrowhead)" d="M600,250.6757L600,207.2249"/>
<text fill="#e74c3c" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="670" y="245" data-gn-el-type="text" style="white-space:pre"><tspan>Remove from top</tspan></text>
<text fill="#e74c3c" font-family="Arial, sans-serif" font-size="14" font-weight="700" xml:space="preserve" x="485" y="347" data-gn-el-type="text" style="white-space:pre"><tspan>TOP ➜</tspan></text>
</g>
<g id="explanation" transform="translate(0, -105)">
<rect fill="#ffffff" stroke="#bdc3c7" stroke-width="2" rx="8" ry="0" x="50" y="430" width="700" height="55"/>
<text fill="#2c3e50" text-anchor="middle" font-family="Arial, sans-serif" font-size="15" font-weight="700" xml:space="preserve" x="400" y="453" data-gn-el-type="text" style="white-space:pre"><tspan>Stack Behavior: The last plate you add is the first plate you take.</tspan></text>
<text fill="#7f8c8d" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" xml:space="preserve" x="400" y="473" data-gn-el-type="text" style="white-space:pre"><tspan>You can only add or remove plates from the TOP of the stack.</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

@@ -0,0 +1,68 @@
<svg viewBox="0 0 800 650" xmlns="http://www.w3.org/2000/svg">
<!-- Background (REQUIRED) - MUST BE FIRST ELEMENT -->
<rect width="800" height="650" fill="#f8f9fa" rx="10"/>
<!-- Title -->
<text x="80" y="40" font-family="Arial, sans-serif" font-size="28" font-weight="bold" fill="#2c3e50">Two Pointers Decision Rule</text>
<!-- Start -->
<rect x="300" y="80" width="200" height="60" fill="#ffffff" stroke="#3498db" stroke-width="2" rx="8"/>
<text x="340" y="115" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2c3e50">New Problem</text>
<path d="M 400,140 L 400,160" stroke="#dee2e6" stroke-width="2" fill="none"/>
<!-- Q1 -->
<rect x="175" y="160" width="450" height="70" fill="#ffffff" stroke="#3498db" stroke-width="2" rx="8"/>
<text x="210" y="185" font-family="Arial, sans-serif" font-size="16" fill="#2c3e50">Am I maintaining a contiguous interval?</text>
<text x="225" y="205" font-family="Arial, sans-serif" font-size="14" fill="#7f8c8d">(Subarray, substring, window property)</text>
<path d="M 625,195 L 675,195 L 675,225" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="640" y="190" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#27ae60">Yes</text>
<rect x="600" y="225" width="160" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="8"/>
<text x="615" y="255" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="#2c3e50">Sliding Window</text>
<path d="M 400,230 L 400,265" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="410" y="245" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#e74c3c">No</text>
<!-- Q2 -->
<rect x="175" y="265" width="450" height="70" fill="#ffffff" stroke="#3498db" stroke-width="2" rx="8"/>
<text x="225" y="290" font-family="Arial, sans-serif" font-size="16" fill="#2c3e50">Can I safely eliminate one side?</text>
<text x="250" y="310" font-family="Arial, sans-serif" font-size="14" fill="#7f8c8d">(Sorted, symmetric, bottleneck)</text>
<path d="M 625,300 L 675,300 L 675,330" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="640" y="295" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#27ae60">Yes</text>
<rect x="600" y="330" width="160" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="8"/>
<text x="615" y="360" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="#2c3e50">Opposite Dir</text>
<path d="M 400,335 L 400,370" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="410" y="350" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#e74c3c">No</text>
<!-- Q3 -->
<rect x="175" y="370" width="450" height="70" fill="#ffffff" stroke="#3498db" stroke-width="2" rx="8"/>
<text x="225" y="395" font-family="Arial, sans-serif" font-size="16" fill="#2c3e50">Am I rewriting or filtering in place?</text>
<text x="235" y="415" font-family="Arial, sans-serif" font-size="14" fill="#7f8c8d">(Remove dups, move zeros, partition)</text>
<path d="M 625,405 L 675,405 L 675,435" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="640" y="400" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#27ae60">Yes</text>
<rect x="600" y="435" width="160" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="8"/>
<text x="635" y="465" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="#2c3e50">Same Dir</text>
<path d="M 400,440 L 400,475" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="410" y="455" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#e74c3c">No</text>
<!-- Q4 -->
<rect x="175" y="475" width="450" height="70" fill="#ffffff" stroke="#3498db" stroke-width="2" rx="8"/>
<text x="210" y="500" font-family="Arial, sans-serif" font-size="16" fill="#2c3e50">Am I reasoning about relative position?</text>
<text x="250" y="520" font-family="Arial, sans-serif" font-size="14" fill="#7f8c8d">(Midpoint, cycle, fixed gap)</text>
<path d="M 625,510 L 675,510 L 675,540" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="640" y="505" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#27ae60">Yes</text>
<rect x="600" y="540" width="160" height="50" fill="#d1fae5" stroke="#10b981" stroke-width="2" rx="8"/>
<text x="625" y="570" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="#2c3e50">Fast &amp; Slow</text>
<path d="M 400,545 L 400,580" stroke="#dee2e6" stroke-width="2" fill="none"/>
<text x="410" y="560" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#e74c3c">No</text>
<!-- Default -->
<rect x="300" y="580" width="200" height="60" fill="#e5e7eb" stroke="#6b7280" stroke-width="2" rx="8"/>
<text x="325" y="615" font-family="Arial, sans-serif" font-size="16" fill="#2c3e50">Other Techniques</text>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

+5
View File
@@ -29,7 +29,9 @@ export default [
{ from: "/71", to: "/stack/simplify-path" },
{ from: "/73", to: "/matrix-index-math/set-matrix-zeroes" },
{ from: "/74", to: "/binary-search/search-a-2d-matrix" },
{ from: "/76", to: "/sliding-window/minimum-window-substring" },
{ from: "/78", to: "/backtracking/subsets" },
{ from: "/80", to: "/two-pointers/remove-duplicates-from-sorted-array-ii" },
{ from: "/84", to: "/monotonic-stack/largest-rectangle-in-histogram" },
{ from: "/98", to: "/binary-search-tree/validate-binary-search-tree" },
{ from: "/102", to: "/tree-bfs/binary-tree-level-order-traversal" },
@@ -60,6 +62,7 @@ export default [
{ from: "/211", to: "/trie/design-add-and-search-words-data-structure" },
{ from: "/215", to: "/heap-priority-queue/kth-largest-element-in-an-array" },
{ from: "/217", to: "/hash-table/contains-duplicate" },
{ from: "/224", to: "/stack/basic-calculator" },
{ from: "/226", to: "/tree-dfs/invert-binary-tree" },
{ from: "/230", to: "/binary-search-tree/kth-smallest-element-in-a-bst" },
{ from: "/235", to: "/binary-search-tree/lowest-common-ancestor-of-a-binary-search-tree" },
@@ -78,6 +81,7 @@ export default [
{ from: "/424", to: "/sliding-window/longest-repeating-character-replacement" },
{ from: "/435", to: "/intervals/non-overlapping-intervals" },
{ from: "/451", to: "/hash-table/sort-characters-by-frequency" },
{ from: "/496", to: "/monotonic-stack/next-greater-element-i" },
{ from: "/525", to: "/prefix-sum/contiguous-array" },
{ from: "/560", to: "/prefix-sum/subarray-sum-equals-k" },
{ from: "/567", to: "/sliding-window/permutation-in-string" },
@@ -87,6 +91,7 @@ export default [
{ from: "/739", to: "/monotonic-stack/daily-temperatures" },
{ from: "/853", to: "/monotonic-stack/car-fleet" },
{ from: "/875", to: "/binary-search/koko-eating-bananas" },
{ from: "/901", to: "/monotonic-stack/online-stock-span" },
{ from: "/973", to: "/heap-priority-queue/k-closest-points-to-origin" },
{ from: "/974", to: "/prefix-sum/subarray-sums-divisible-by-k" },
{ from: "/977", to: "/two-pointers/squares-of-a-sorted-array" },
@@ -0,0 +1,75 @@
"""
496. Next Greater Element I
Difficulty: Easy
https://leetcode.com/problems/next-greater-element-i/
──────────────────────────────────────────────────
The next greater element of some element x in an array is the first
greater element that is to the right of x in the same array.
You are given two distinct 0-indexed integer arrays nums1 and nums2,
where nums1 is a subset of nums2.
For each 0 <= i < nums1.length, find the index j such that nums1[i]
== nums2[j] and determine the next greater element of nums2[j] in
nums2. If there is no next greater element, then the answer for this
query is -1.
Return an array ans of length nums1.length such that ans[i] is the
next greater element as described above.
Example 1:
Input: nums1 = [4,1,2], nums2 = [1,3,4,2]
Output: [-1,3,-1]
Explanation: The next greater element for each value of nums1 is as
follows:
- 4 is underlined in nums2 = [1,3,4,2]. There is no next greater
element, so the answer is -1.
- 1 is underlined in nums2 = [1,3,4,2]. The next greater element is 3.
- 2 is underlined in nums2 = [1,3,4,2]. There is no next greater
element, so the answer is -1.
Example 2:
Input: nums1 = [2,4], nums2 = [1,2,3,4]
Output: [3,-1]
Explanation: The next greater element for each value of nums1 is as
follows:
- 2 is underlined in nums2 = [1,2,3,4]. The next greater element is 3.
- 4 is underlined in nums2 = [1,2,3,4]. There is no next greater
element, so the answer is -1.
Constraints:
• 1 <= nums1.length <= nums2.length <= 1000
• 0 <= nums1[i], nums2[i] <= 10^4
• All integers in nums1 and nums2 are unique.
• All the integers of nums1 also appear in nums2.
Follow up: Could you find an O(nums1.length + nums2.length) solution?
"""
class Solution:
def nextGreaterElement(self, nums1: list[int], nums2: list[int]) -> list[int]:
mono_stack = []
next_greater = {}
for cur in nums2:
while mono_stack and cur > mono_stack[-1]:
val = mono_stack.pop()
next_greater[val] = cur
mono_stack.append(cur)
return [next_greater.get(x, -1) for x in nums1]
@@ -0,0 +1,50 @@
"""
876. Middle of the Linked List
Difficulty: Easy
https://leetcode.com/problems/middle-of-the-linked-list/
──────────────────────────────────────────────────
Given the head of a singly linked list, return the middle node of the
linked list.
If there are two middle nodes, return the second middle node.
Example 1:
Input: head = [1,2,3,4,5]
Output: [3,4,5]
Explanation: The middle node of the list is node 3.
Example 2:
Input: head = [1,2,3,4,5,6]
Output: [4,5,6]
Explanation: Since the list has two middle nodes with values 3 and 4,
we return the second one.
Constraints:
• The number of nodes in the list is in the range [1, 100].
• 1 <= Node.val <= 100
"""
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def middleNode(self, head: ListNode | None) -> ListNode | None:
slow = fast = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
return slow
@@ -0,0 +1,56 @@
"""
76. Minimum Window Substring
Difficulty: Hard
https://leetcode.com/problems/minimum-window-substring/
──────────────────────────────────────────────────
Given two strings s and t of lengths m and n respectively, return the
minimum window substring of s such that every character in t
(including duplicates) is included in the window. If there is no such
substring, return the empty string "".
The testcases will be generated such that the answer is unique.
Example 1:
Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC"
Explanation: The minimum window substring "BANC" includes 'A', 'B',
and 'C' from string t.
Example 2:
Input: s = "a", t = "a"
Output: "a"
Explanation: The entire string s is the minimum window.
Example 3:
Input: s = "a", t = "aa"
Output: ""
Explanation: Both 'a's from t must be included in the window.
Since the largest window of s only has one 'a', return empty string.
Constraints:
• m == s.length
• n == t.length
• 1 <= m, n <= 10^5
• s and t consist of uppercase and lowercase English letters.
Follow up: Could you find an algorithm that runs in O(m + n) time?
"""
class Solution:
def minWindow(self, s: str, t: str) -> str:
+55
View File
@@ -0,0 +1,55 @@
"""
224. Basic Calculator
Difficulty: Hard
https://leetcode.com/problems/basic-calculator/
──────────────────────────────────────────────────
Given a string s representing a valid expression, implement a basic
calculator to evaluate it, and return the result of the evaluation.
Note: You are not allowed to use any built-in function which
evaluates strings as mathematical expressions, such as eval().
Example 1:
Input: s = "1 + 1"
Output: 2
Example 2:
Input: s = " 2-1 + 2 "
Output: 3
Example 3:
Input: s = "(1+(4+5+2)-3)+(6+8)"
Output: 23
Constraints:
• 1 <= s.length <= 3 * 10^5
• s consists of digits, '+', '-', '(', ')', and ' '.
• s represents a valid expression.
• '+' is not used as a unary operation (i.e., "+1" and "+(2 + 3)" is
invalid).
• '-' could be used as a unary operation (i.e., "-1" and "-(2 + 3)"
is valid).
• There will be no two consecutive operators in the input.
• Every number and running calculation will fit in a signed 32-bit
integer.
"""
class Solution:
def calculate(self, s: str) -> int:
+93
View File
@@ -0,0 +1,93 @@
"""
36. Valid Sudoku
Difficulty: Medium
https://leetcode.com/problems/valid-sudoku/
──────────────────────────────────────────────────
Determine if a 9 x 9 Sudoku board is valid. Only the filled cells
need to be validated according to the following rules:
• Each row must contain the digits 1-9 without repetition.
• Each column must contain the digits 1-9 without repetition.
• Each of the nine 3 x 3 sub-boxes of the grid must contain the
digits 1-9 without repetition.
Note:
• A Sudoku board (partially filled) could be valid but is not
necessarily solvable.
• Only the filled cells need to be validated according to the
mentioned rules.
Example 1:
Input: board =
[["5","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]
Output: true
Example 2:
Input: board =
[["8","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]
Output: false
Explanation: Same as Example 1, except with the 5 in the top left
corner being modified to 8. Since there are two 8's in the top left
3x3 sub-box, it is invalid.
Constraints:
• board.length == 9
• board[i].length == 9
• board[i][j] is a digit 1-9 or '.'.
"""
import collections
class Solution:
def isValidSudoku(self, board: list[list[str]]) -> bool:
cols = collections.defaultdict(set)
rows = collections.defaultdict(set)
squares = collections.defaultdict(set) # (r/3, c/3)
for r in range(9):
for c in range(9):
if board[r][c] == ".":
continue
if (
board[r][c] in rows[r]
or board[r][c] in cols[c]
or board[r][c] in squares[(r // 3, c // 3)]
):
return False
cols[c].add(board[r][c])
rows[r].add(board[r][c])
squares[(r // 3, c // 3)].add(board[r][c])
return True
@@ -0,0 +1,79 @@
"""
80. Remove Duplicates from Sorted Array II
Difficulty: Medium
https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/
──────────────────────────────────────────────────
Given an integer array nums sorted in non-decreasing order, remove
some duplicates in-place such that each unique element appears at most
twice. The relative order of the elements should be kept the same.
Since it is impossible to change the length of the array in some
languages, you must instead have the result be placed in the first
part of the array nums. More formally, if there are k elements after
removing the duplicates, then the first k elements of nums should hold
the final result. It does not matter what you leave beyond the first k
elements.
Return k after placing the final result in the first k slots of nums.
Do not allocate extra space for another array. You must do this by
modifying the input array in-place with O(1) extra memory.
Custom Judge:
The judge will test your solution with the following code:
int[] nums = [...]; // Input array
int[] expectedNums = [...]; // The expected answer with correct length
int k = removeDuplicates(nums); // Calls your implementation
assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
assert nums[i] == expectedNums[i];
}
If all assertions pass, then your solution will be accepted.
Example 1:
Input: nums = [1,1,1,2,2,3]
Output: 5, nums = [1,1,2,2,3,_]
Explanation: Your function should return k = 5, with the first five
elements of nums being 1, 1, 2, 2 and 3 respectively.
It does not matter what you leave beyond the returned k (hence they
are underscores).
Example 2:
Input: nums = [0,0,1,1,1,1,2,3,3]
Output: 7, nums = [0,0,1,1,2,3,3,_,_]
Explanation: Your function should return k = 7, with the first seven
elements of nums being 0, 0, 1, 1, 2, 3 and 3 respectively.
It does not matter what you leave beyond the returned k (hence they
are underscores).
Constraints:
• 1 <= nums.length <= 3 * 10^4
• -10^4 <= nums[i] <= 10^4
• nums is sorted in non-decreasing order.
"""
class Solution:
def removeDuplicates(self, nums: list[int]) -> int:
slow = 0
for fast in range(len(nums)):
if slow < 2 or nums[fast] != nums[slow - 2]:
nums[slow] = nums[fast]
slow += 1
return slow
@@ -0,0 +1,69 @@
"""
19. Remove Nth Node From End of List
Difficulty: Medium
https://leetcode.com/problems/remove-nth-node-from-end-of-list/
──────────────────────────────────────────────────
Given the head of a linked list, remove the n^th node from the end of
the list and return its head.
Example 1:
Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]
Example 2:
Input: head = [1], n = 1
Output: []
Example 3:
Input: head = [1,2], n = 1
Output: [1]
Constraints:
• The number of nodes in the list is sz.
• 1 <= sz <= 30
• 0 <= Node.val <= 100
• 1 <= n <= sz
Follow up: Could you do this in one pass?
"""
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode | None, n: int) -> ListNode | None:
dummy = ListNode(0)
dummy.next = head
fast = dummy
for _ in range(n):
if fast.next is None:
return head
fast = fast.next
slow = dummy
while fast.next is not None:
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return dummy.next
@@ -0,0 +1,80 @@
"""
901. Online Stock Span
Difficulty: Medium
https://leetcode.com/problems/online-stock-span/
──────────────────────────────────────────────────
Design an algorithm that collects daily price quotes for some stock
and returns the span of that stock's price for the current day.
The span of the stock's price in one day is the maximum number of
consecutive days (starting from that day and going backward) for which
the stock price was less than or equal to the price of that day.
• For example, if the prices of the stock in the last four days are
[7,2,1,2] and the price of the stock today is 2, then the span of
today is 4 because starting from today, the price of the stock was
less than or equal to 2 for 4 consecutive days.
• Also, if the prices of the stock in the last four days is
[7,34,1,2] and the price of the stock today is 8, then the span of
today is 3 because starting from today, the price of the stock was
less than or equal 8 for 3 consecutive days.
Implement the StockSpanner class:
• StockSpanner() Initializes the object of the class.
• int next(int price) Returns the span of the stock's price given
that today's price is price.
Example 1:
Input
["StockSpanner", "next", "next", "next", "next", "next", "next",
"next"]
[[], [100], [80], [60], [70], [60], [75], [85]]
Output
[null, 1, 1, 1, 2, 1, 4, 6]
Explanation
StockSpanner stockSpanner = new StockSpanner();
stockSpanner.next(100); // return 1
stockSpanner.next(80); // return 1
stockSpanner.next(60); // return 1
stockSpanner.next(70); // return 2
stockSpanner.next(60); // return 1
stockSpanner.next(75); // return 4, because the last 4 prices
(including today's price of 75) were less than or equal to today's
price.
stockSpanner.next(85); // return 6
Constraints:
• 1 <= price <= 10^5
• At most 10^4 calls will be made to next.
"""
class StockSpanner:
def __init__(self):
self.stack = []
def next(self, price: int) -> int:
ans = 1
while self.stack and self.stack[-1][0] <= price:
ans += self.stack.pop()[1]
self.stack.append([price, ans])
return ans
# Your StockSpanner object will be instantiated and called as such:
# obj = StockSpanner()
# param_1 = obj.next(price)