Compare commits

..
Author SHA1 Message Date
Prad Nukala 26085cc027 docs(docs): add Word Ladder and Rotting Oranges documentation pages 2026-09-22 12:59:21 -04:00
Prad Nukala 216152d946 init: hooks 2026-09-22 12:59:13 -04:00
Prad Nukala d0ace85bbc docs(agents): add .githooks entry describing git hook enforcement and pre‑push sync check 2026-09-22 12:59:00 -04:00
Prad Nukala 5b99a6a589 feat(work): add Word Ladder and Rotting Oranges solutions 2026-09-22 12:58:56 -04:00
Prad Nukala 8446623607 chore(config): add prepare script for git hooks 2026-09-22 12:58:54 -04:00
Prad Nukala 3a1df27d30 docs(readme): replace static chart images with live SRS API badges and update description. 2026-09-22 11:58:58 -04:00
Prad Nukala ae1d1ceede feat(work): implement Pacific Atlantic Water Flow solution 2026-09-21 18:05:35 -04:00
Prad Nukala f5f31ba1c3 docs(docs): remove array order meta and add solution implementation for Pacific Atlantic Water Flow 2026-09-21 18:05:30 -04:00
Prad Nukala d1c99c307e docs(docs): add order to array meta 2026-09-21 17:41:19 -04:00
Prad Nukala 021b1fd06e docs(agents): update docs for topic folder naming and sync behavior 2026-09-21 17:37:31 -04:00
Prad Nukala edd37ed0e6 docs: rename category folders to remove parentheses and update heap meta title 2026-09-21 17:37:26 -04:00
Prad Nukala c2a8a31ede chore(config): remove Reference tab from navigation 2026-09-21 17:37:22 -04:00
Prad Nukala 891a1f0c5a refactor(cli): clarify topic folder handling and remove unused groupFolder helper 2026-09-21 17:37:19 -04:00
Prad Nukala 2e5d991d47 chore(config): bump multiple package versions in bun.lock 2026-09-21 17:16:31 -04:00
Prad Nukala 73d63c2565 docs(docs): clarify apps/docs layout and bump blume to ^1.7.3 2026-09-21 17:16:18 -04:00
Prad Nukala cafc8a4c25 feat(work): add solutions for number-of-islands, pacific-atlantic-water-flow, and clone-graph problems 2026-09-21 17:16:13 -04:00
Prad Nukala 7c5061dcd1 docs(docs): add meta files and reorganize topics with updated badges and directory structure 2026-09-21 17:16:10 -04:00
Prad Nukala f9e69960c4 feat(cli): add topic grouping with meta.ts generation in sync script 2026-09-21 17:15:56 -04:00
Prad Nukala 0c12df3eba docs(readme): update LeetCode profile link to progress page 2026-09-21 15:55:59 -04:00
Prad Nukala bfb53c14e8 docs(readme): wrap LeetCode badge in profile link 2026-09-21 15:54:29 -04:00
Prad Nukala 2c30b3d017 feat(problem-approach): add skill for generating approach diagrams 2026-09-21 15:53:32 -04:00
Prad Nukala 1084410832 docs(readme): add videos command and update schedule dates and badges 2026-09-21 15:53:28 -04:00
Prad Nukala 76c9c77307 feat(work): add Number of Islands solution 2026-09-21 15:53:24 -04:00
Prad Nukala 23eb71d387 docs(docs): add Approach diagrams and Explanation sections to LeetCode problem documentation 2026-09-21 15:53:21 -04:00
Prad Nukala b8c779903f feat(config): add videos script to package.json 2026-09-21 15:53:18 -04:00
Prad Nukala 5059eb72cb feat(cli): add videos script for NeetCode embed lookup and CLI command 2026-09-21 15:53:15 -04:00
104 changed files with 1997 additions and 393 deletions

No files matched your search

+150
View File
@@ -0,0 +1,150 @@
---
name: problem-approach
description: Write the `## Approach` section — a Mermaid flowchart that traces the submitted solution — for every docs solution page under apps/docs/content/ that lacks one, using the repo's exact placement and node conventions. Use when asked to add/implement/fill in missing approaches, draw the approach diagram for a problem page, or when `/problem-approach` is run.
---
# Problem approach diagrams
Every solution page (`apps/docs/content/(<category>)/<num>-<slug>.mdx`) has three
hand-owned sections around the script-owned one:
```
frontmatter → <Badge> → ::::warning (maybe) → ### Example N / ### Constraints
## Approach ← this skill (Mermaid flowchart of the code below)
## Solution ← bun run sync owns this; never edit inside it
## Explanation ← bun run videos --write (NeetCode embed); not this skill
```
`sync.ts` regenerates only `## Solution`, so an `## Approach` placed **before** it
survives every re-sync; anything placed inside `## Solution` is overwritten on the
next `work/` push.
## The command: fill every missing approach
1. **Find the pages** (numbered pages with no `## Approach` heading):
```sh
grep -L '^## Approach' apps/docs/content/*/*.mdx
```
2. **For each page**, read `## Solution` and write the diagram from *that code* — the
one on the page, not the textbook algorithm. The reader compares diagram and code
line by line; the diagram is a trace, not a lecture.
3. **Insert** the section between the `### Constraints:` list and `## Solution`,
blank-line separated on both sides:
````md
- `-100 <= Node.val <= 100`
## Approach
```mermaid
flowchart TD
…
```
## Solution
````
4. **Check** each diagram by hand against the syntax rules below (the docs build does
not parse Mermaid — a broken diagram fails only in the browser), then run the
docs build once at the end and review the pages in `bun run dev`:
```sh
cd apps/docs && bunx blume build --isolated && rm -rf .blume-verify
```
Pages with two languages (`<CodeGroup>`) get **one** diagram, traced from the
Python version. Pages with only JavaScript trace the JavaScript.
## Diagram conventions
Reference page: `apps/docs/content/(array)/15-3sum.mdx`. Every existing diagram
follows this shape:
```mermaid
flowchart TD
S(["threeSum(nums)"]) --> O["nums.sort() — duplicates become adjacent"]
O --> F{"more i in 0..n-1?"}
F -- no --> E(["return result"])
F -- yes --> D{"nums[i] == nums[i-1]?"}
D -- yes --> F
D -- no --> P["left = i+1, right = n-1, target = -nums[i]"]
P --> W{"left < right?"}
W -- no --> F
W -- yes --> C["current = nums[left] + nums[right]"]
C --> Q{"current vs target"}
Q -- equal --> A["append triplet, skip equal neighbours, left += 1, right -= 1"]
A --> W
Q -- "current < target" --> L["left += 1 — need a bigger sum"]
L --> W
Q -- "current > target" --> R["right -= 1 — need a smaller sum"]
R --> W
```
- `flowchart TD`, two-space indent, one edge per line, edges listed in execution
order (entry first, exits as soon as they are reachable).
- **Entry** is a stadium holding the signature: `S(["threeSum(nums)"])`. **Every
exit** is a stadium: `E(["return result"])`, `R(["return true"])`.
- **Statements** are rectangles `["…"]`, in the code's own identifiers and operators
(`left += 1`, `freq[n] = (freq[n] or 0) + 1`). Several trivial assignments may share
one node, comma-separated.
- **Decisions** are diamonds `{"…?"}` with `-- yes -->` / `-- no -->` edges. A
three-way branch names the outcomes: `-- equal -->`, `-- "current < target" -->`
(quote an edge label whenever it holds `<`, `>`, or spaces around operators).
- **Loops** are a decision: `for` → `{"more n in nums?"}`, `while` → the condition
verbatim `{"left < right?"}`. `-- no -->` goes to whatever follows the loop; the
body's last node edges back to the decision. `continue` is an edge back to the loop
decision (`D -- yes --> F` above); `break` is an edge to the node after the loop.
- **Recursion** is a statement, not an expanded subgraph: `A["left = invertTree(root.left)"]`.
The base case is the first decision after entry.
- **Classes** (design problems) use one `subgraph name["method(args)"]` per public
method, plus a `state["…"]` subgraph describing the fields, with `state --> method`
edges — see `(hash-table)/146-lru-cache.mdx`. Private helpers are named inside the
method nodes (`"_remove_node then _add_node — move it beside the tail"`), not drawn.
- **Annotate the why with an em dash** inside the node, sparingly (one clause, a few
per diagram): `"nums.sort() — duplicates become adjacent"`, `"map = {0: 1} — the
empty prefix"`. The dash explains the invariant that makes the step correct — the
one thing the code does not say.
- **Node ids**: one or two capitals, unique per diagram (`S I L Q C E R W A B`). Class
diagrams prefix per method (`G1`, `P1`).
### Syntax rules (a diagram that breaks the page is worse than none)
- Wrap **every** label in double quotes — brackets, parentheses, `<`, `>` and `|` in an
unquoted label break the parser.
- Never put `"` inside a label; use `'` for string literals (`"s == ''?"`).
- Never use `|` anywhere — not in node labels, not as `-->|text|` edge syntax; edge
labels are always `-- text -->`. Write `or` for a logical or.
- Every id an edge references must be declared with a shape the first time it appears.
- Balanced: each `subgraph` has an `end`; each fence opens ```` ```mermaid ```` and closes ```` ``` ````.
### Code that does not run
Trace it anyway — the diagram documents what was submitted — and put a `::::warning`
callout **before** the fence naming exactly why it fails (`(array)/121-best-time-to-buy-and-sell-stock.mdx`):
````md
## Approach
::::warning
The diagram traces the code below literally. That code does not run: `curr` and `ans` are never initialised, so the first `curr > left` test raises `NameError`.
::::
```mermaid
…
```
````
Do not fix the code on the page: `## Solution` mirrors `work/1`, and the fix belongs
there (then `bun run sync`).
## Do not
- Do not write prose approaches, complexity tables or alternative algorithms — the
section is one flowchart (plus the warning callout when the code is broken).
- Do not touch `## Solution` or anything after it; `## Explanation` is filled by
`bun run videos --write`.
- Do not add an approach to `reference/` pages or `index.mdx` — only numbered
solution pages (`title: 'NN. …'`) carry one.
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# Pre-push: the docs pages under apps/docs/content are generated from work/1
# by `bun run sync`, and deploy.yml publishes whatever main holds. Regenerate
# them now and refuse the push if the result differs from the tree, so a
# solution never lands without its page (or with a stale `## Solution`).
#
# Bypass for a deliberate partial push: git push --no-verify
set -eu
cd "$(git rev-parse --show-toplevel)"
report=$(bun run --silent sync)
changed=$(git status --porcelain -- apps/docs/content)
if [ -n "$changed" ]; then
printf '%s\n' "$report" | grep -v 'skipped (in sync)' >&2
printf '\npre-push: apps/docs/content is out of sync with work/1 — commit the regenerated pages:\n%s\n' "$changed" >&2
exit 1
fi
+6 -2
View File
@@ -44,9 +44,10 @@ flowchart LR
|---|---|
| `work/<1\|3\|7>/<Difficulty>/<Category>/<num>.<slug>.{js,py}` | Solutions, e.g. `work/1/Easy/Array/1.two-sum.py`. The top directory is the spaced-repetition bucket: `1` = first solve, `3`/`7` = the blind re-solves of that problem 3 and 7 days later. Machine-parsed header comment (title / `Difficulty:` / URL / `─` rule / statement) — preserve its exact shape |
| `apps/cli/` | Flat Bun TS workspace: automation entries (shebang + top-level await) and libraries (no shebang, side-effect-free on import). Root `bun run` scripts delegate here |
| `apps/docs/` | Blume site (`blume.config.ts`, `content/`, `islands/`, `public/`). `content/(<category>)/<num>-<slug>.mdx` generated by sync |
| `apps/docs/` | Blume site (`blume.config.ts`, `content/`, `islands/`, `public/`). `content/<topic>/<num>-<slug>.mdx` plus one `meta.ts` per topic folder, all generated by sync: the folder is the README campaign topic slug (`tree-dfs/`, `heap-priority-queue/`) and a real route segment (`/tree-dfs/invert-binary-tree`), the `meta.ts` carries the exact label and topic number so the sidebar reads in curriculum order, and the page badge is the `work/` data-structure category. A hand-written `<topic>/index.mdx` (`hidden: true`) is that topic's landing page: Blume ≥ 1.7.3 links the sidebar group row to it (`sliding-window/index.mdx` → `/sliding-window`) and the hidden flag drops the duplicate row |
| `apps/api/` | Cloudflare Worker workspace: `src/` modules, `data/schedule.json` (the plan: `date → { topics | problems | gate | sweep }`, human-edited, bundled at deploy), `migrations/` (append-only; `0002` is the +3/+7 ladder rebuild) |
| `.github/workflows/` | deploy (docs → Pages, Worker → Cloudflare, on every main push), close-solved (issues + `/admin/solved` + Project `Set`/`Difficulty`, needs `SRS_ADMIN_KEY` and `PROJECT_PAT`), close-topics, sync-d1 (issue edits → `/admin/reconcile`), spaced-repetition (daily 06:00 ET cron → today's review issue; `GITHUB_TOKEN` only, no secrets) |
| `.githooks/` | Tracked git hooks; `bun install` points `core.hooksPath` here (root `prepare` script, no husky). `pre-push` runs `bun run sync` and refuses the push while `apps/docs/content` differs from the tree, so a solution never reaches `main` (and `deploy.yml`) without its page. `git push --no-verify` for a deliberate partial push |
## Development Commands
@@ -67,6 +68,9 @@ bun run test # run ONE solution against LeetCode's judge (not a test
bun run submit # submit ONE solution to LeetCode (newest scaffold first, nothing
# hidden; last_submitted stamped in the db on exit 0)
bun run sync # work/1 → apps/docs/content/ pages (re-solves are never published)
bun run videos # docs pages with no <YouTube> embed → NeetCode video per page
# (channel search scraped, no API key; --write appends the
# `## Explanation` embed; "no NeetCode match" pages stay untouched)
bun run dev|build # Blume docs site (runs in apps/docs/)
bun run close-solved -- --dry-run # issue reconcilers (also DRY_RUN=1)
bun run close-topics -- --dry-run
@@ -102,7 +106,7 @@ curl -X POST -H "Authorization: Bearer $LINK_KEY" \
- `apps/api/src/index.ts` — router + cron dispatch; `apps/api/wrangler.jsonc` — bindings (`DB`, `EMAIL`), crons, vars; secrets `GH_PAT`/`WEBHOOK_SECRET`/`LINK_KEY` via `wrangler secret put`.
- `apps/api/src/digest.ts` / `apps/api/src/email.tsx` — the daily digest, split data/presentation. `digest.ts` reads D1 into a `DigestData`; `email.tsx` owns every colour and every sentence, and renders both the HTML and (via its own `plainDigest`, not React Email's `plainText` mode, which flattens the tables) the text alternative. The retrieval rules hold by construction: `DigestRow` has no title and no issue field, so a review or drill line *cannot* leak the topic or a solution link. Subject is `(Day N/56) LeetCode Daily Digest`.
- `apps/api/src/png.ts` — hand-rolled PNG encoder (RGB8, one IDAT, zlib via `CompressionStream("deflate")`, CRC32, 5×7 bitmap font). It exists because every major email client refuses remote SVG, so `/chart/heatmap.png` rasters the heatmap for the digest while `/chart/heatmap.svg` keeps serving the README byte-for-byte. Both come from one `heatmapCells()` so the two pictures cannot drift.
- `apps/cli/sync.ts` — work→docs contract: on existing pages only the `## Solution` section itself is script-owned, bounded by `solutionEnd()` at the next unfenced top-level heading; human prose before it and any sections after it (`## Approach`, `## Explanation`) are never touched. Never hand-write solution pages or edit inside `## Solution`.
- `apps/cli/sync.ts` — work→docs contract: on existing pages only the `<Badge>` line and the `## Solution` section are script-owned, the latter bounded by `solutionEnd()` at the next unfenced top-level heading; human prose before it and any sections after it are never touched. Never hand-write solution pages or edit inside `## Solution`. Two classifications, two homes: the sidebar group is the README topic (`readmeTopics()`, first listing wins for a problem under two topics; a warm-up outside the plan groups under its category, unordered, after the topics) and the badge is the `work/1/<Difficulty>/<Category>/` category — topic folders and their `meta.ts` are rewritten every run, a page is `rename`d when its topic changes, and a folder every page has left is deleted unless it still holds a hand-written `index.mdx` (sync never writes, moves, or deletes one). Never use `(group)` folders here: they add no route segment, so a topic `index.mdx` inside one collides with the root `/`. The two hand-owned sections around the solution have their own owners: `## Approach` (a Mermaid flowchart tracing the page's code, placed *before* `## Solution` so a re-sync keeps it) is written per the `problem-approach` skill (`/problem-approach` fills every page missing one); `## Explanation` (the NeetCode `<YouTube>` embed, after `## Solution`) is appended by `bun run videos --write` (`apps/cli/videos.ts`).
- `apps/cli/work.ts` — the `work/` layout: bucket paths, `isSolution()`/`solvedInBucket()` (the stub test, shared with `close-solved.ts`), `firstSolved()` (one `git log`, ET dates, keyed by LC number so past layout moves don't matter), and `reviewQueues()` (what each window still owes). Everything that needs to know where a solution file lives, or when it was solved, goes through here.
- `apps/api/src/stats.ts` / `apps/cli/stats.ts` / `apps/cli/tui.ts` / `apps/cli/api.ts` — the read side. `buildStats()` is the ONE aggregation: phases, ladder, difficulty, per-topic temperature, gates, the 14-day due queue, and `heat` (every campaign day, zeros and future days included — the docs page windows the tail off it rather than asking a second question). Two readers consume it, the docs island and `bun run stats`, so its shape is both their contract: change all three together. `tui.ts` is hand-rolled ANSI (bars, sparklines, heat cells, alternate screen) on charts.ts's palette, and every chart differs by GLYPH as well as by colour so a piped or `NO_COLOR` dashboard still reads. The TUI recomputes nothing: the only local fact it shows is the `work/` solution count, which is inventory, not schedule.
- `README.md` live charts are Worker endpoints (`/chart/*.svg`, `/badge/gate.svg`, 5-min Camo cache); the docs `/progress` page fetches `/api/stats` client-side (`apps/docs/islands/ProgressDashboard.tsx`).
+11 -11
View File
@@ -4,6 +4,7 @@
<p align="center">
<a href="https://x.com/basedprad"><img alt="follow" src="https://shieldcn.dev/x/follow/basedprad.svg" /></a>
<a href="https://leetcode.com/progress"><img alt="Custom badge" src="https://shieldcn.dev/badge/view-@prdlk.svg?logo=leetcode" /></a>
<a href="https://lc.prad.nu"><img alt="badge" src="https://shieldcn.dev/badge/Read-Documentation.svg?logo=lu%3ABook" /></a>
<a href="https://lc.prad.nu/progress"><img alt="gate pass rate" src="https://srs-api.prdlk.workers.dev/badge/gate.svg" /></a>
</p>
@@ -212,7 +213,7 @@
<tr><td align="center"><code>B</code> ⭐</td><td align="center">295</td><td><a href="https://leetcode.com/problems/find-median-from-data-stream/">Find Median from Data Stream</a></td></tr>
<tr><td align="center"><code>B</code> ⭐</td><td align="center">502</td><td><a href="https://leetcode.com/problems/ipo/">IPO</a></td></tr>
<tr>
<td rowspan="3"><a href="https://github.com/prdlk/leetcode/issues/17"><strong>15. Trie</strong></a><br><sub>Wk 6 · Mon Sep 21</sub><br><sub>builds on 11</sub></td>
<td rowspan="3"><a href="https://github.com/prdlk/leetcode/issues/17"><strong>15. Trie</strong></a><br><sub>Wk 4 · Wed Sep 9</sub><br><sub>builds on 11</sub></td>
<td align="center"><code>A</code></td><td align="center">208</td><td><a href="https://leetcode.com/problems/implement-trie-prefix-tree/">Implement Trie</a></td>
</tr>
<tr><td align="center"><code>A</code></td><td align="center">211</td><td><a href="https://leetcode.com/problems/design-add-and-search-words-data-structure/">Add &amp; Search Words</a></td></tr>
@@ -234,7 +235,7 @@
</thead>
<tbody>
<tr>
<td rowspan="7"><a href="https://github.com/prdlk/leetcode/issues/18"><strong>16. Graph DFS</strong></a><br><sub>Wk 6 · Mon Sep 21</sub><br><sub>builds on 11 + visited set</sub></td>
<td rowspan="7"><a href="https://github.com/prdlk/leetcode/issues/18"><strong>16. Graph DFS</strong></a><br><sub>Wk 4 · Thu Sep 10</sub><br><sub>builds on 11 + visited set</sub></td>
<td align="center"><code>A</code></td><td align="center">200</td><td><a href="https://leetcode.com/problems/number-of-islands/">Number of Islands</a></td>
</tr>
<tr><td align="center"><code>A</code></td><td align="center">695</td><td><a href="https://leetcode.com/problems/max-area-of-island/">Max Area of Island</a></td></tr>
@@ -244,7 +245,7 @@
<tr><td align="center"><code>B</code></td><td align="center">399</td><td><a href="https://leetcode.com/problems/evaluate-division/">Evaluate Division</a></td></tr>
<tr><td align="center"><code>B</code></td><td align="center">261</td><td><a href="https://leetcode.com/problems/graph-valid-tree/">Graph Valid Tree</a></td></tr>
<tr>
<td rowspan="5"><a href="https://github.com/prdlk/leetcode/issues/19"><strong>17. Graph BFS</strong></a><br><sub>Wk 6 · Tue Sep 22</sub><br><sub>builds on 12 + visited set — shortest path unweighted</sub></td>
<td rowspan="5"><a href="https://github.com/prdlk/leetcode/issues/19"><strong>17. Graph BFS</strong></a><br><sub>Wk 4 · Fri Sep 11</sub><br><sub>builds on 12 + visited set — shortest path unweighted</sub></td>
<td align="center"><code>A</code></td><td align="center">994</td><td><a href="https://leetcode.com/problems/rotting-oranges/">Rotting Oranges</a></td>
</tr>
<tr><td align="center"><code>A</code></td><td align="center">127</td><td><a href="https://leetcode.com/problems/word-ladder/">Word Ladder</a></td></tr>
@@ -252,14 +253,14 @@
<tr><td align="center"><code>B</code></td><td align="center">433</td><td><a href="https://leetcode.com/problems/minimum-genetic-mutation/">Minimum Genetic Mutation</a></td></tr>
<tr><td align="center"><code>B</code></td><td align="center">286</td><td><a href="https://leetcode.com/problems/walls-and-gates/">Walls and Gates</a></td></tr>
<tr>
<td rowspan="4"><a href="https://github.com/prdlk/leetcode/issues/20"><strong>18. Topological Sort</strong></a><br><sub>Wk 6 · Tue Sep 22</sub><br><sub>builds on 16</sub></td>
<td rowspan="4"><a href="https://github.com/prdlk/leetcode/issues/20"><strong>18. Topological Sort</strong></a><br><sub>Wk 5 · Mon Sep 14</sub><br><sub>builds on 16</sub></td>
<td align="center"><code>A</code></td><td align="center">207</td><td><a href="https://leetcode.com/problems/course-schedule/">Course Schedule</a></td>
</tr>
<tr><td align="center"><code>A</code></td><td align="center">210</td><td><a href="https://leetcode.com/problems/course-schedule-ii/">Course Schedule II</a></td></tr>
<tr><td align="center"><code>B</code> ⭐</td><td align="center">269</td><td><a href="https://leetcode.com/problems/alien-dictionary/">Alien Dictionary</a></td></tr>
<tr><td align="center"><code>B</code></td><td align="center">1462</td><td><a href="https://leetcode.com/problems/course-schedule-iv/">Course Schedule IV</a></td></tr>
<tr>
<td rowspan="4"><a href="https://github.com/prdlk/leetcode/issues/21"><strong>19. Union-Find</strong></a><br><sub>Wk 6 · Wed Sep 23</sub><br><sub>builds on 16's problem class</sub></td>
<td rowspan="4"><a href="https://github.com/prdlk/leetcode/issues/21"><strong>19. Union-Find</strong></a><br><sub>Wk 5 · Tue Sep 15</sub><br><sub>builds on 16's problem class</sub></td>
<td align="center"><code>A</code></td><td align="center">684</td><td><a href="https://leetcode.com/problems/redundant-connection/">Redundant Connection</a></td>
</tr>
<tr><td align="center"><code>A</code></td><td align="center">323</td><td><a href="https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/">Number of Connected Components</a></td></tr>
@@ -282,7 +283,7 @@
</thead>
<tbody>
<tr>
<td rowspan="8"><a href="https://github.com/prdlk/leetcode/issues/22"><strong>20. Backtracking</strong></a><br><sub>Wk 6 · Thu Sep 24</sub><br><sub>builds on 16 — then unlock LC 212 from topic 15</sub></td>
<td rowspan="8"><a href="https://github.com/prdlk/leetcode/issues/22"><strong>20. Backtracking</strong></a><br><sub>Wk 5 · Wed Sep 16</sub><br><sub>builds on 16 — then unlock LC 212 from topic 15</sub></td>
<td align="center"><code>A</code></td><td align="center">78</td><td><a href="https://leetcode.com/problems/subsets/">Subsets</a></td>
</tr>
<tr><td align="center"><code>A</code></td><td align="center">39</td><td><a href="https://leetcode.com/problems/combination-sum/">Combination Sum</a></td></tr>
@@ -293,7 +294,7 @@
<tr><td align="center"><code>B</code></td><td align="center">22</td><td><a href="https://leetcode.com/problems/generate-parentheses/">Generate Parentheses</a></td></tr>
<tr><td align="center"><code>B</code> ⭐</td><td align="center">52</td><td><a href="https://leetcode.com/problems/n-queens-ii/">N-Queens II</a></td></tr>
<tr>
<td rowspan="9"><a href="https://github.com/prdlk/leetcode/issues/23"><strong>21. 1-D Dynamic Programming</strong></a><br><sub>Wk 6 · Fri Sep 25</sub><br><sub>builds on 20</sub></td>
<td rowspan="9"><a href="https://github.com/prdlk/leetcode/issues/23"><strong>21. 1-D Dynamic Programming</strong></a><br><sub>Wk 5 · Thu Sep 17</sub><br><sub>builds on 20</sub></td>
<td align="center"><code>A</code></td><td align="center">70</td><td><a href="https://leetcode.com/problems/climbing-stairs/">Climbing Stairs</a></td>
</tr>
<tr><td align="center"><code>A</code></td><td align="center">746</td><td><a href="https://leetcode.com/problems/min-cost-climbing-stairs/">Min Cost Climbing Stairs</a></td></tr>
@@ -305,7 +306,7 @@
<tr><td align="center"><code>B</code></td><td align="center">91</td><td><a href="https://leetcode.com/problems/decode-ways/">Decode Ways</a></td></tr>
<tr><td align="center"><code>B</code></td><td align="center">647</td><td><a href="https://leetcode.com/problems/palindromic-substrings/">Palindromic Substrings</a></td></tr>
<tr>
<td rowspan="8"><a href="https://github.com/prdlk/leetcode/issues/24"><strong>22. Multi-D / Grid DP</strong></a><br><sub>Wk 7 · Mon Sep 28</sub><br><sub>builds on 21</sub></td>
<td rowspan="8"><a href="https://github.com/prdlk/leetcode/issues/24"><strong>22. Multi-D / Grid DP</strong></a><br><sub>Wk 5 · Fri Sep 18</sub><br><sub>builds on 21</sub></td>
<td align="center"><code>A</code></td><td align="center">62</td><td><a href="https://leetcode.com/problems/unique-paths/">Unique Paths</a></td>
</tr>
<tr><td align="center"><code>A</code></td><td align="center">64</td><td><a href="https://leetcode.com/problems/minimum-path-sum/">Minimum Path Sum</a></td></tr>
@@ -316,7 +317,7 @@
<tr><td align="center"><code>B</code></td><td align="center">221</td><td><a href="https://leetcode.com/problems/maximal-square/">Maximal Square</a></td></tr>
<tr><td align="center"><code>B</code> ⭐</td><td align="center">97</td><td><a href="https://leetcode.com/problems/interleaving-string/">Interleaving String</a></td></tr>
<tr>
<td rowspan="7"><a href="https://github.com/prdlk/leetcode/issues/25"><strong>23. Greedy</strong></a><br><sub>Wk 7 · Tue Sep 29</sub><br><sub>builds on 21 — includes Kadane's</sub></td>
<td rowspan="7"><a href="https://github.com/prdlk/leetcode/issues/25"><strong>23. Greedy</strong></a><br><sub>Wk 6 · Mon Sep 21</sub><br><sub>builds on 21 — includes Kadane's</sub></td>
<td align="center"><code>A</code></td><td align="center">53</td><td><a href="https://leetcode.com/problems/maximum-subarray/">Maximum Subarray</a></td>
</tr>
<tr><td align="center"><code>A</code></td><td align="center">55</td><td><a href="https://leetcode.com/problems/jump-game/">Jump Game</a></td></tr>
@@ -326,7 +327,7 @@
<tr><td align="center"><code>B</code></td><td align="center">918</td><td><a href="https://leetcode.com/problems/maximum-sum-circular-subarray/">Maximum Sum Circular Subarray</a></td></tr>
<tr><td align="center"><code>B</code></td><td align="center">763</td><td><a href="https://leetcode.com/problems/partition-labels/">Partition Labels</a></td></tr>
<tr>
<td rowspan="7"><a href="https://github.com/prdlk/leetcode/issues/26"><strong>24. Intervals</strong></a><br><sub>Wk 7 · Wed Sep 30</sub><br><sub>builds on 23</sub></td>
<td rowspan="7"><a href="https://github.com/prdlk/leetcode/issues/26"><strong>24. Intervals</strong></a><br><sub>Wk 6 · Tue Sep 22</sub><br><sub>builds on 23</sub></td>
<td align="center"><code>A</code></td><td align="center">57</td><td><a href="https://leetcode.com/problems/insert-interval/">Insert Interval</a></td>
</tr>
<tr><td align="center"><code>A</code></td><td align="center">56</td><td><a href="https://leetcode.com/problems/merge-intervals/">Merge Intervals</a></td></tr>
@@ -337,4 +338,3 @@
<tr><td align="center"><code>B</code></td><td align="center">252</td><td><a href="https://leetcode.com/problems/meeting-rooms/">Meeting Rooms I</a></td></tr>
</tbody>
</table>
+2 -1
View File
@@ -8,12 +8,13 @@
"test": "bun ./test.ts",
"submit": "bun ./submit.ts",
"sync": "bun ./sync.ts",
"videos": "bun ./videos.ts",
"close-solved": "bun ./close-solved.ts",
"close-topics": "bun ./close-topics.ts"
},
"devDependencies": {
"@clack/prompts": "^1.7.0",
"@night-slayer18/leetcode-cli": "^3.5.0",
"@night-slayer18/leetcode-cli": "^3.5.2",
"@types/bun": "^1.3.14"
}
}
+31 -17
View File
@@ -30,17 +30,27 @@ function lcNumber(rel: string): number | undefined {
}
/** Fuzzy-pick a solution under work/ and run `leetcode <command>` on it. */
export async function runOnSolution(command: "test" | "submit", message: string): Promise<never> {
export async function runOnSolution(
command: "test" | "submit",
message: string,
): Promise<never> {
const db = problemDb();
const scanned = [...new Bun.Glob("**/*.{js,ts,py,java,c,cpp,go,rs,rb,swift,kt,cs}").scanSync({ cwd: WORK_DIR })];
const scanned = [
...new Bun.Glob("**/*.{js,ts,py,java,c,cpp,go,rs,rb,swift,kt,cs}").scanSync(
{ cwd: WORK_DIR },
),
];
// register creation times so files older than the db still sort correctly
for (const f of scanned) {
const num = lcNumber(f);
if (num === undefined) continue;
const stat = statSync(join(WORK_DIR, f));
db.ensureSolution(num, new Date(stat.birthtimeMs || stat.mtimeMs).toISOString());
db.ensureSolution(
num,
new Date(stat.birthtimeMs || stat.mtimeMs).toISOString(),
);
}
const activity = db.solutionActivity();
@@ -51,20 +61,21 @@ export async function runOnSolution(command: "test" | "submit", message: string)
return (command === "test" ? row?.lastTested : row?.createdAt) ?? undefined;
};
const files = scanned
.sort((a, b) => {
const ka = sortKey(a);
const kb = sortKey(b);
if (ka !== kb) {
if (ka === undefined) return 1;
if (kb === undefined) return -1;
return kb.localeCompare(ka); // ISO timestamps: lexicographic = chronological
}
return a.localeCompare(b, undefined, { numeric: true });
});
const files = scanned.sort((a, b) => {
const ka = sortKey(a);
const kb = sortKey(b);
if (ka !== kb) {
if (ka === undefined) return 1;
if (kb === undefined) return -1;
return kb.localeCompare(ka); // ISO timestamps: lexicographic = chronological
}
return a.localeCompare(b, undefined, { numeric: true });
});
if (files.length === 0) {
log.error(`No solution files found in ${relative(process.cwd(), WORK_DIR)}/`);
log.error(
`No solution files found in ${relative(process.cwd(), WORK_DIR)}/`,
);
process.exit(1);
}
@@ -74,11 +85,14 @@ export async function runOnSolution(command: "test" | "submit", message: string)
maxItems: 12,
options: files.map((f) => {
const parsed = parseWorkPath(f);
const stage = parsed?.bucket === 1 ? "first solve" : `${parsed?.bucket}-day review`;
const stage =
parsed?.bucket === 1 ? "first solve" : `${parsed?.bucket}-day review`;
return {
value: f,
label: basename(f),
hint: parsed ? `${stage} · ${parsed.difficulty} · ${parsed.category}` : f,
hint: parsed
? `${stage} · ${parsed.difficulty} · ${parsed.category}`
: f,
};
}),
filter: (search, option) => {
+73 -17
View File
@@ -4,18 +4,31 @@
*
* - New problems get a full page (frontmatter, badge, warning, examples,
* constraints, solution) in the gold-standard format.
* - Already-ported pages only get their `## Solution` section regenerated,
* so hand-curated prose is never touched.
* - Already-ported pages only get their script-owned parts regenerated — the
* `<Badge>` line and the `## Solution` section — so hand-curated prose is
* never touched.
* - When a problem is solved in both JavaScript and Python the solution
* renders as a <CodeGroup> (Python first); single-language solutions
* render as a plain fence.
*
* Two classifications, two homes. The sidebar group is the campaign TOPIC
* from the README table (`tree-dfs/`, ordered by topic number via a
* script-owned `meta.ts`); the page badge is the data-structure CATEGORY the
* file sits under in `work/1/<Difficulty>/<Category>/`. A problem outside the
* plan (warm-ups) has no topic, so it groups under its category instead.
*
* Topic folders are real route segments (`/tree-dfs/invert-binary-tree`), not
* `(group)` folders, so a hand-written `index.mdx` in one is the topic's
* landing page at `/tree-dfs` and Blume links the sidebar group row to it.
* Sync never writes or moves an `index.mdx`, and never removes a folder that
* still holds one.
*
* Only the first-solve bucket is read: work/3 and work/7 hold blind re-solves
* of problems whose page already exists (see work.ts), and a fresh scaffold
* there would otherwise wipe the page's `## Solution`.
*/
import { mkdir, rename } from "node:fs/promises";
import { basename, join } from "node:path";
import { mkdir, readdir, rename, rmdir, unlink } from "node:fs/promises";
import { basename, dirname, join } from "node:path";
const ROOT = join(import.meta.dir, "..", "..");
const WORK = join(ROOT, "work", "1");
@@ -158,24 +171,37 @@ function parseHeader(header: string): Header {
// ── README topic map ─────────────────────────────────────────────
/** A campaign topic: `<strong>NN. Name</strong>` in the README table. */
interface Topic {
order?: number; // README topic number; absent for an unplanned problem's category group
name: string;
}
const TOPIC_ALIASES: Record<string, string> = {
"Hash-Based Lookup": "Hash Table",
};
async function readmeTopics(): Promise<Map<number, string>> {
/** LC number -> topic. A problem listed under two topics keeps the first. */
async function readmeTopics(): Promise<Map<number, Topic>> {
const md = await Bun.file(join(ROOT, "README.md")).text();
const map = new Map<number, string>();
let topic = "";
const map = new Map<number, Topic>();
let topic: Topic | null = null;
for (const line of md.split("\n")) {
const t = line.match(/<strong>\d+\.\s*([^<]+)<\/strong>/);
if (t) topic = TOPIC_ALIASES[t[1]!.trim()] ?? t[1]!.trim();
const t = line.match(/<strong>(\d+)\.\s*([^<]+)<\/strong>/);
if (t) topic = { order: Number(t[1]), name: TOPIC_ALIASES[t[2]!.trim()] ?? t[2]!.trim() };
if (!topic) continue;
const n = line.match(/<td align="center">(\d+)<\/td>/);
if (n) map.set(Number(n[1]), topic);
if (n && !map.has(Number(n[1]))) map.set(Number(n[1]), topic);
}
return map;
}
/** Blume folder meta: exact topic label (the folder slug loses `/` and case) and curriculum order. */
function groupMeta(topic: Topic): string {
const order = topic.order === undefined ? "" : `, order: ${topic.order}`;
return `import { defineMeta } from "blume";\n\nexport default defineMeta({ title: ${JSON.stringify(topic.name)}${order} });\n`;
}
// ── inline-code formatting heuristics ────────────────────────────
/** Identifiers worth backticking in prose, harvested from the code. */
@@ -277,7 +303,11 @@ function warningText(h: Header): string {
);
}
function renderPage(sol: Solution, topic: string): string {
function badgeLine(sol: Solution): string {
return `<Badge variant="accent">${sol.category}</Badge>`;
}
function renderPage(sol: Solution): string {
const h = sol.header;
const ids = identifiers(sol);
const description = h.statement[0]?.replace(/\.\s*$/, "") ?? "";
@@ -292,7 +322,7 @@ function renderPage(sol: Solution, topic: string): string {
` badge: '${h.difficulty}'`,
"---",
"",
`<Badge variant="accent">${topic}</Badge>`,
badgeLine(sol),
"",
);
@@ -387,12 +417,19 @@ for (const rel of (await Array.fromAsync(new Bun.Glob("**/*.mdx").scan(DOCS))).s
const report: [string, string, string][] = [];
const ordered = [...solutions.values()].sort((a, b) => a.num - b.num);
const groups = new Map<string, Topic>(); // folder -> topic, for meta.ts
const vacated = new Set<string>(); // folders a page moved out of; removed below if empty
for (const sol of ordered) {
const langs = Object.keys(sol.code).sort().join("+");
const workRef = `${sol.num}.${sol.slug} (${langs})`;
// (category) group folder + leetcode-number prefix: numeric sidebar order, both stripped from the URL.
const group = `(${sol.category.toLowerCase().replaceAll(" ", "-")})`;
// <topic>/ folder (a real route segment, so its index.mdx is the topic landing
// page) + leetcode-number prefix: numeric sidebar order, prefix stripped from the URL.
// An unplanned problem's category may share a folder with a numbered topic
// (`Hash Table` warm-ups under topic 1); the numbered one owns the meta.
const topic = topics.get(sol.num) ?? { name: sol.category };
const group = topic.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
if (groups.get(group)?.order === undefined) groups.set(group, topic);
const name = `${group}/${sol.num}-${sol.slug}.mdx`;
const existing = pages.get(sol.num);
await mkdir(join(DOCS, group), { recursive: true });
@@ -406,10 +443,11 @@ for (const sol of ordered) {
}
const tail = page.slice(solutionEnd(page, at));
const body = page.slice(0, at) + solutionSection(sol) + (tail ? `\n${tail}` : "");
const next = ensureLabel(body, sol.header);
const next = ensureLabel(body, sol.header).replace(/^<Badge variant="accent">.*<\/Badge>$/m, badgeLine(sol));
const actions: string[] = [];
if (existing !== name) {
await rename(join(DOCS, existing), join(DOCS, name));
vacated.add(dirname(existing));
actions.push("moved");
}
if (next !== page) {
@@ -418,12 +456,30 @@ for (const sol of ordered) {
}
report.push([workRef, name, actions.join(" + ") || "skipped (in sync)"]);
} else {
const topic = topics.get(sol.num) ?? sol.category;
await Bun.write(join(DOCS, name), renderPage(sol, topic));
await Bun.write(join(DOCS, name), renderPage(sol));
report.push([workRef, name, "created"]);
}
}
// Script-owned folder meta: one per live group, rewritten only when it differs.
for (const [group, topic] of groups) {
const path = join(DOCS, group, "meta.ts");
const meta = groupMeta(topic);
if ((await Bun.file(path).exists()) && (await Bun.file(path).text()) === meta) continue;
await Bun.write(path, meta);
report.push([`topic ${topic.order ?? "-"}`, `${group}/meta.ts`, "written"]);
}
// A group every page left behind is dead: drop its meta and the folder itself.
for (const dir of vacated) {
if (groups.has(dir)) continue;
const left = await readdir(join(DOCS, dir));
if (left.some((f) => f !== "meta.ts")) continue;
if (left.length) await unlink(join(DOCS, dir, "meta.ts"));
await rmdir(join(DOCS, dir));
report.push(["", dir, "removed (empty)"]);
}
const w0 = Math.max(...report.map((r) => r[0].length), 9);
const w1 = Math.max(...report.map((r) => r[1].length), 9);
console.log(`${"work file".padEnd(w0)} ${"docs page".padEnd(w1)} status`);
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env bun
/**
* Find the NeetCode explanation video for every docs page that has none.
*
* A finished solution page ends with `## Explanation` holding one
* `<YouTube id="…" title="…" />` — NeetCode's walkthrough of the problem.
* `sync.ts` never writes that section (it owns only `## Solution`), so pages
* accumulate without one. This script lists every numbered page lacking a
* `<YouTube` embed and looks each up on NeetCode's two channels, main channel
* first, accepting only a title that names the page's exact LC number
* (`… - Leetcode 226 - …`) and is not a #short. No YouTube API key: the
* channel search page embeds its results as `ytInitialData` JSON, which is
* all we parse. Ranking is the channel's own — the first exact-number match
* wins.
*
* bun apps/cli/videos.ts # report: page → video (or "no match")
* bun apps/cli/videos.ts --write # also append `## Explanation` + embed
*
* --write appends at end of file (below `## Solution`, where sync.ts leaves
* human sections alone) and adds the `## Explanation` heading only when the
* page lacks it. A page with no match is left untouched, so re-runs are
* no-ops once every page has its embed.
*/
import { join } from "node:path";
import { printTable } from "./report.ts";
import { ROOT } from "./work.ts";
const DOCS = join(ROOT, "apps", "docs", "content");
const WRITE = process.argv.includes("--write");
/** Channel handles searched in order; the first exact match wins. */
const CHANNELS = ["NeetCode", "NeetCodeIO"] as const;
interface Video {
id: string;
title: string;
channel: string;
}
// ── YouTube channel search (no API key) ──────────────────────────
const HEADERS = { "accept-language": "en-US,en;q=0.9", "user-agent": "Mozilla/5.0" };
/**
* The `var ytInitialData = {…};</script>` blob of a YouTube page, parsed.
* YouTube intermittently answers 200 with a page that lacks the blob or cuts
* it short, so a bad page is retried a few times before it is an error.
*/
async function initialData(url: string): Promise<unknown> {
let reason = "";
for (let attempt = 0; attempt < 3; attempt++) {
if (attempt) await Bun.sleep(500 * attempt);
const res = await fetch(url, { headers: HEADERS });
if (!res.ok) {
reason = `status ${res.status}`;
continue;
}
const html = await res.text();
// Slice rather than regex: the blob is ~1 MB, and a lazy `.*?` across it
// is what made the first cut of this script fail at random.
const open = html.search(/ytInitialData\s*=\s*\{/);
const close = open === -1 ? -1 : html.indexOf("</script>", open);
if (close === -1) {
reason = `no ytInitialData in ${html.length}-byte page`;
continue;
}
try {
return JSON.parse(html.slice(html.indexOf("{", open), close).replace(/;\s*$/, ""));
} catch {
reason = "truncated ytInitialData";
}
}
throw new Error(`GET ${url} -> ${reason}`);
}
/** Every videoRenderer in a channel search page, in the page's own order. */
async function channelSearch(channel: string, query: string): Promise<Video[]> {
const url = `https://www.youtube.com/@${channel}/search?query=${encodeURIComponent(query)}`;
const data = await initialData(url);
const videos: Video[] = [];
const walk = (node: unknown): void => {
if (!node || typeof node !== "object") return;
if ("videoRenderer" in node) {
const v = node.videoRenderer;
if (v && typeof v === "object" && "videoId" in v && typeof v.videoId === "string") {
const runs =
"title" in v && v.title && typeof v.title === "object" && "runs" in v.title
? v.title.runs
: undefined;
const title = Array.isArray(runs)
? runs.map((r) => (r && typeof r === "object" && "text" in r ? String(r.text) : "")).join("")
: "";
videos.push({ id: v.videoId, title, channel });
}
return;
}
for (const child of Object.values(node)) walk(child);
};
walk(data);
return videos;
}
/** The LC number a NeetCode title names (`… - Leetcode 226 - …`), if any. */
function titledNumber(title: string): number | undefined {
const m = title.match(/\bleetcode\s*#?\s*(\d+)\b/i);
return m ? Number(m[1]) : undefined;
}
async function findVideo(num: number): Promise<Video | undefined> {
for (const channel of CHANNELS) {
const hit = (await channelSearch(channel, `leetcode ${num}`)).find(
(v) => titledNumber(v.title) === num && !/#shorts\b/i.test(v.title),
);
if (hit) return hit;
}
return undefined;
}
// ── docs pages ───────────────────────────────────────────────────
interface Page {
num: number;
rel: string;
text: string;
}
/** Numbered solution pages without a `<YouTube` embed, by LC number. */
async function pagesMissingVideo(): Promise<Page[]> {
const pages: Page[] = [];
for (const rel of await Array.fromAsync(new Bun.Glob("**/*.mdx").scan(DOCS))) {
const text = await Bun.file(join(DOCS, rel)).text();
const t = text.match(/^title: '(\d+)\./m);
if (t && !text.includes("<YouTube")) pages.push({ num: Number(t[1]), rel, text });
}
return pages.sort((a, b) => a.num - b.num);
}
function withEmbed(page: Page, video: Video): string {
const embed = `<YouTube id="${video.id}" title="${video.title.replaceAll('"', "'")}" />`;
const heading = /^## Explanation\s*$/m.test(page.text) ? "" : "## Explanation\n\n";
return `${page.text.replace(/\s*$/, "\n")}\n${heading}${embed}\n`;
}
// ── main ─────────────────────────────────────────────────────────
const missing = await pagesMissingVideo();
if (missing.length === 0) {
console.log("every solution page has a video");
process.exit(0);
}
const rows: string[][] = [["docs page", "video", "title", WRITE ? "status" : ""]];
let found = 0;
for (const page of missing) {
const video = await findVideo(page.num);
if (!video) {
rows.push([page.rel, "—", "no NeetCode match", WRITE ? "skipped" : ""]);
continue;
}
found++;
let status = "";
if (WRITE) {
await Bun.write(join(DOCS, page.rel), withEmbed(page, video));
status = "written";
}
rows.push([page.rel, `https://youtu.be/${video.id}`, `${video.title} (${video.channel})`, status]);
}
if (!WRITE) for (const row of rows) row.pop();
printTable(rows);
console.log(`\n${found}/${missing.length} page(s) matched${WRITE ? `, ${found} written` : ""}`);
-8
View File
@@ -10,12 +10,4 @@ export default defineConfig({
content: {
root: "content",
},
navigation: {
tabs: [
{
label: "Reference",
path: "/reference",
},
],
},
});
@@ -27,6 +27,27 @@ Could you write a generalized algorithm to work on any possible set of character
- `0 <= strs[i].length <= 200`
- `strs[i]` contains any possible characters out of 256 valid ASCII characters.
## Approach
::::warning
The diagram traces the code below literally. That code does not solve the problem: both `encode` and `decode` have only a docstring for a body, so each returns `None` and `codec.decode(codec.encode(strs))` yields `None` instead of `strs`.
::::
```mermaid
flowchart TD
subgraph state["state — Codec holds no fields"]
X["Codec() — nothing is stored between encode and decode"]
end
subgraph encode["encode(strs)"]
E1["docstring only — no statement runs"] --> E2(["return None"])
end
subgraph decode["decode(s)"]
D1["docstring only — no statement runs"] --> D2(["return None"])
end
state --> encode
state --> decode
```
## Solution
```py
@@ -46,3 +67,7 @@ class Codec:
# codec = Codec()
# codec.decode(codec.encode(strs))
```
## Explanation
<YouTube id="B1k_sxOSgv8" title="Encode and Decode Strings - Leetcode 271 - Python" />
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Array" });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Binary Search Tree</Badge>
<Badge variant="accent">Tree</Badge>
::::warning
If the BST is modified often (i.e., we can do insert and delete operations) and you need to find the kth smallest frequently, how would you optimize?
@@ -26,6 +26,23 @@ If the BST is modified often (i.e., we can do insert and delete operations) and
- `1 <= k <= n <= 10^4`
- `0 <= Node.val <= 10^4`
## Approach
```mermaid
flowchart TD
S(["kthSmallest(root, k)"]) --> I["n = 0, stack = [], cur = root"]
I --> W{"cur or stack?"}
W -- no --> E(["fall off the loop — unreachable, k <= n"])
W -- yes --> L{"cur?"}
L -- yes --> P["stack.append(cur), cur = cur.left — push the whole left spine"]
P --> L
L -- no --> O["cur = stack.pop(), n += 1 — nodes pop in ascending order"]
O --> K{"n == k?"}
K -- yes --> R(["return cur.val"])
K -- no --> N["cur = cur.right"]
N --> W
```
## Solution
```py
@@ -52,3 +69,7 @@ class Solution:
return cur.val
cur = cur.right
```
## Explanation
<YouTube id="5LUXSvjmGCw" title="Kth Smallest Element in a BST - Leetcode 230 - Python" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Binary Search Tree</Badge>
<Badge variant="accent">Tree</Badge>
### Example 1:
- Input: `root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8`
@@ -30,6 +30,22 @@ sidebar:
- `p != q`
- p and q will exist in the BST.
## Approach
```mermaid
flowchart TD
S(["lowestCommonAncestor(root, p, q)"]) --> I["cur = root"]
I --> W{"cur?"}
W -- no --> E(["fall off the loop — unreachable, p and q exist in the BST"])
W -- yes --> A{"p.val < cur.val and q.val < cur.val?"}
A -- yes --> L["cur = cur.left — both sit in the left subtree"]
L --> W
A -- no --> B{"p.val > cur.val and q.val > cur.val?"}
B -- yes --> R["cur = cur.right — both sit in the right subtree"]
R --> W
B -- no --> C(["return cur — the paths to p and q split here"])
```
## Solution
```py
@@ -54,3 +70,7 @@ class Solution:
else:
return cur
```
## Explanation
<YouTube id="gs2LMfuOR9k" title="Lowest Common Ancestor of a Binary Search Tree - Leetcode 235 - Python" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Binary Search Tree</Badge>
<Badge variant="accent">Tree</Badge>
### Example 1:
- Input: `root = [2,1,3]`
@@ -22,6 +22,23 @@ sidebar:
- The number of nodes in the tree is in the range [1, 10^4].
- `-2^31 <= Node.val <= 2^31 - 1`
## Approach
```mermaid
flowchart TD
S(["isValidBST(root)"]) --> C["valid(root, float('-inf'), float('inf')) — the root may hold any value"]
C --> E(["return that result"])
subgraph valid["valid(node, low, high)"]
V1{"not node?"}
V1 -- yes --> V2(["return True"])
V1 -- no --> V3{"not (low < node.val < high)?"}
V3 -- yes --> V4(["return False — node breaks a bound set by an ancestor"])
V3 -- no --> V5["valid(node.left, low, node.val) and valid(node.right, node.val, high) — each child tightens one bound"]
V5 --> V6(["return that result"])
end
C --> V1
```
## Solution
```py
@@ -44,3 +61,7 @@ class Solution:
return valid(root, float("-inf"), float("inf"))
```
## Explanation
<YouTube id="s6ATEkipzow" title="Validate Binary Search Tree - Depth First Search - Leetcode 98" />
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Binary Search Tree", order: 13 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Binary Search</Badge>
<Badge variant="accent">Array</Badge>
::::warning
Notice that rotating an array [a[0], a[1], a[2], ..., a[n-1]] 1 time results in the array [a[n-1], a[0], a[1], a[2], ..., a[n-2]].
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Binary Search</Badge>
<Badge variant="accent">Array</Badge>
::::warning
You must write an algorithm with O(log n) runtime complexity.
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Binary Search</Badge>
<Badge variant="accent">Array</Badge>
::::warning
You must write an algorithm with O(log n) runtime complexity.
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Binary Search</Badge>
<Badge variant="accent">Array</Badge>
::::warning
You must write an algorithm with O(log n) runtime complexity.
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Binary Search</Badge>
<Badge variant="accent">Array</Badge>
::::warning
You must write a solution in O(log(m * n)) time complexity.
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Binary Search</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `piles = [3,6,7,11], h = 8`
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Binary Search", order: 5 });
@@ -0,0 +1,67 @@
---
title: '127. Word Ladder'
description: 'A transformation sequence from word beginWord to word endWord using a dictionary wordList is a sequence of words beginWord -> s1 -> s2 -> ... -> sk such that:'
sidebar:
label: 'Word Ladder'
badge: 'Hard'
---
<Badge variant="accent">Hash Table</Badge>
### Example 1:
- Input: `beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]`
- Output: `5`
- Explanation: One shortest transformation sequence is "hit" -> "hot" -> "dot" -> "dog" -> cog", which is `5` words long.
### Example 2:
- Input: `beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]`
- Output: `0`
- Explanation: The `endWord` "cog" is not in `wordList`, therefore there is no valid transformation sequence.
### Constraints:
- `1 <= beginWord.length <= 10`
- `endWord.length == beginWord.length`
- `1 <= wordList.length <= 5000`
- `wordList[i].length == beginWord.length`
- `beginWord`, `endWord`, and `wordList[i]` consist of lowercase English letters.
- `beginWord != endWord`
- All the words in `wordList` are unique.
## Solution
```py
import collections
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: list[str]) -> int:
if endWord not in wordList:
return 0
adj = collections.defaultdict(list)
wordList.append(beginWord)
for word in wordList:
for j in range(len(word)):
pattern = word[:j] + "*" + word[j + 1 :]
adj[pattern].append(word)
visit = set([beginWord])
q = collections.deque([beginWord])
res = 1
while q:
for i in range(len(q)):
word = q.popleft()
if word == endWord:
return res
for j in range(len(word)):
pattern = word[:j] + "*" + word[j + 1 :]
for adjWord in adj[pattern]:
if adjWord not in visit:
visit.add(adjWord)
q.append(adjWord)
res += 1
return 0
```
@@ -0,0 +1,72 @@
---
title: '994. Rotting Oranges'
description: 'You are given an m x n grid where each cell can have one of three values:'
sidebar:
label: 'Rotting Oranges'
badge: 'Medium'
---
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `grid = [[2,1,1],[1,1,0],[0,1,1]]`
- Output: `4`
### Example 2:
- Input: `grid = [[2,1,1],[0,1,1],[1,0,1]]`
- Output: `-1`
- Explanation: The orange in the bottom left corner (row `2`, column `0`) is never rotten, because rotting only happens 4-directionally.
### Example 3:
- Input: `grid = [[0,2]]`
- Output: `0`
- Explanation: Since there are already no fresh oranges at minute `0`, the answer is just `0`.
### Constraints:
- `m == grid.length`
- `n == grid[i].length`
- `1 <= m, n <= 10`
- grid[i][j] is 0, 1, or 2.
## Solution
```py
from collections import deque
class Solution:
def orangesRotting(self, grid: list[list[int]]) -> int:
q = deque()
time, fresh = 0, 0
rows, cols = len(grid), len(grid[0])
for r in range(rows):
for c in range(cols):
if grid[r][c] == 1:
fresh += 1
if grid[r][c] == 2:
q.append([r, c])
directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]
while q and fresh > 0:
for i in range(len(q)):
r, c = q.popleft()
for dr, dc in directions:
row, col = dr + r, dc + c
# check if in bounds
if (
row < 0
or col < 0
or row == len(grid)
or col == len(grid[0])
or grid[row][col] != 1
):
continue
grid[row][col] = 2
q.append([row, col])
fresh -= 1
time += 1
return time if fresh == 0 else -1
```
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Graph BFS", order: 17 });
@@ -0,0 +1,64 @@
---
title: '133. Clone Graph'
description: Given a reference of a node in a connected undirected graph
sidebar:
label: 'Clone Graph'
badge: 'Medium'
---
<Badge variant="accent">Hash Table</Badge>
### Example 1:
- Input: `adjList = [[2,4],[1,3],[2,4],[1,3]]`
- Output: `[[2,4],[1,3],[2,4],[1,3]]`
- Explanation: There are `4` nodes in the graph. 1st `node` (val = 1)'s neighbors are 2nd `node` (val = `2`) and 4th `node` (val = `4`). 2nd `node` (val = 2)'s neighbors are 1st `node` (val = `1`) and 3rd `node` (val = `3`). 3rd `node` (val = 3)'s neighbors are 2nd `node` (val = `2`) and 4th `node` (val = `4`). 4th `node` (val = 4)'s neighbors are 1st `node` (val = `1`) and 3rd `node` (val = `3`).
### Example 2:
- Input: `adjList = [[]]`
- Output: `[[]]`
- Explanation: Note that the input contains one empty list. The graph consists of only one `node` with val = `1` and it does not have any neighbors.
### Example 3:
- Input: `adjList = []`
- Output: `[]`
- Explanation: This an empty graph, it does not have any nodes.
### Constraints:
- The number of nodes in the graph is in the range [0, 100].
- `1 <= Node.val <= 100`
- Node.val is unique for each node.
- There are no repeated edges and no self-loops in the graph.
- The Graph is connected and all nodes can be visited starting from the given node.
## Solution
```py
"""
# Definition for a Node.
class Node:
def __init__(self, val = 0, neighbors = None):
self.val = val
self.neighbors = neighbors if neighbors is not None else []
"""
from typing import Optional
class Solution:
def cloneGraph(self, node: Optional["Node"]) -> Optional["Node"]:
oldToNew = {}
def dfs(node):
if node in oldToNew:
return oldToNew[node]
copy = Node(node.val)
oldToNew[node] = copy
for n in node.neighbors:
copy.neighbors.append(dfs(n))
return copy
return dfs(node) if node else None
```
@@ -0,0 +1,51 @@
---
title: '200. Number of Islands'
description: Given an m x n 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands
sidebar:
label: 'Number of Islands'
badge: 'Medium'
---
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ]`
- Output: `1`
### Example 2:
- Input: `grid = [ ["1","1","0","0","0"], ["1","1","0","0","0"], ["0","0","1","0","0"], ["0","0","0","1","1"] ]`
- Output: `3`
### Constraints:
- `m == grid.length`
- `n == grid[i].length`
- `1 <= m, n <= 300`
- grid[i][j] is '0' or '1'.
## Solution
```py
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
rows, cols = len(grid), len(grid[0])
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != "1":
return
else:
grid[r][c] = "0"
dfs(r, c + 1) # right
dfs(r + 1, c) # bottom
dfs(r, c - 1) # left
dfs(r - 1, c) # top
num_islands = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == "1":
num_islands += 1
dfs(r, c)
return num_islands
```
@@ -0,0 +1,67 @@
---
title: '417. Pacific Atlantic Water Flow'
description: There is an m x n rectangular island that borders both the Pacific Ocean and Atlantic Ocean. The Pacific Ocean touches the island's left and top edges, and the Atlantic Ocean touches the island's right and bottom edges
sidebar:
label: 'Pacific Atlantic Water Flow'
badge: 'Medium'
---
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]`
- Output: `[[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]`
- Explanation: The following cells can flow to the Pacific and Atlantic oceans, as shown below: `[0,4]`: `[0,4]` -> Pacific Ocean `[0,4]` -> Atlantic Ocean `[1,3]`: `[1,3]` -> `[0,3]` -> Pacific Ocean `[1,3]` -> `[1,4]` -> Atlantic Ocean `[1,4]`: `[1,4]` -> `[1,3]` -> `[0,3]` -> Pacific Ocean `[1,4]` -> Atlantic Ocean `[2,2]`: `[2,2]` -> `[1,2]` -> `[0,2]` -> Pacific Ocean `[2,2]` -> `[2,3]` -> `[2,4]` -> Atlantic Ocean `[3,0]`: `[3,0]` -> Pacific Ocean `[3,0]` -> `[4,0]` -> Atlantic Ocean `[3,1]`: `[3,1]` -> `[3,0]` -> Pacific Ocean `[3,1]` -> `[4,1]` -> Atlantic Ocean `[4,0]`: `[4,0]` -> Pacific Ocean `[4,0]` -> Atlantic Ocean Note that there are other possible paths for these cells to flow to the Pacific and Atlantic oceans.
### Example 2:
- Input: `heights = [[1]]`
- Output: `[[0,0]]`
- Explanation: The water can flow from the only cell to the Pacific and Atlantic oceans.
### Constraints:
- `m == heights.length`
- `n == heights[r].length`
- `1 <= m, n <= 200`
- `0 <= heights[r][c] <= 10^5`
## Solution
```py
class Solution:
def pacificAtlantic(self, heights: list[list[int]]) -> list[list[int]]:
rows, cols = len(heights), len(heights[0])
pac, atl = set(), set()
def dfs(r, c, seen, prevHeight):
if (
(r, c) in seen
or r < 0
or c < 0
or r == rows
or c == cols
or heights[r][c] < prevHeight
):
return
seen.add((r, c))
dfs(r + 1, c, seen, heights[r][c])
dfs(r - 1, c, seen, heights[r][c])
dfs(r, c + 1, seen, heights[r][c])
dfs(r, c - 1, seen, heights[r][c])
for c in range(cols):
dfs(0, c, pac, heights[0][c])
dfs(rows - 1, c, atl, heights[rows - 1][c])
for r in range(rows):
dfs(r, 0, pac, heights[r][0])
dfs(r, cols - 1, atl, heights[r][cols - 1])
res = []
for r in range(rows):
for c in range(cols):
if (r, c) in pac and (r, c) in atl:
res.append([r, c])
return res
```
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Graph DFS", order: 16 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Hash Table</Badge>
<Badge variant="accent">Array</Badge>
::::warning
Can you come up with an algorithm that is less than O(n^2) time complexity?
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Hash Table</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `nums = [1,2,3,1]`
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Hash Table</Badge>
<Badge variant="accent">Array</Badge>
::::warning
Your algorithm's time complexity must be better than O(n log n), where n is the array's size.
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Hash Table</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `strs = ["eat","tea","tan","ate","nat","bat"]`
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Hash Table", order: 1 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Heap / Priority Queue</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `nums = [3,2,1,5,6,4], k = 2`
@@ -21,6 +21,20 @@ sidebar:
- `1 <= k <= nums.length <= 10^5`
- `-10^4 <= nums[i] <= 10^4`
## Approach
```mermaid
flowchart TD
S(["findKthLargest(nums, k)"]) --> F{"more i in range(len(nums))?"}
F -- yes --> N["nums[i] = -nums[i] — negate so the min-heap acts as a max-heap"]
N --> F
F -- no --> H["heapq.heapify(nums)"]
H --> W{"more _ in range(k - 1)?"}
W -- yes --> P["heapq.heappop(nums) — discard the k-1 largest"]
P --> W
W -- no --> E(["return -heapq.heappop(nums)"])
```
## Solution
```py
@@ -39,3 +53,7 @@ class Solution:
return -heapq.heappop(nums)
```
## Explanation
<YouTube id="XEmy13g1Qxc" title="Kth Largest Element in an Array - Quick Select - Leetcode 215 - Python" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Hard'
---
<Badge variant="accent">Heap / Priority Queue</Badge>
<Badge variant="accent">Linked List</Badge>
### Example 1:
- Input: `lists = [[1,4,5],[1,3,4],[2,6]]`
@@ -30,6 +30,27 @@ sidebar:
- `lists[i]` is sorted in ascending order.
- The sum of lists[i].length will not exceed 10^4.
## Approach
```mermaid
flowchart TD
S(["mergeKLists(lists)"]) --> I["heap = []"]
I --> F{"more i, node in enumerate(lists)?"}
F -- yes --> C{"node?"}
C -- yes --> P["heapq.heappush(heap, (node.val, i, node)) — i breaks ties so nodes are never compared"]
P --> F
C -- no --> F
F -- no --> D["D = ListNode(), cur = D"]
D --> W{"heap?"}
W -- no --> E(["return D.next"])
W -- yes --> O["val, i, node = heapq.heappop(heap) — the smallest head across all lists"]
O --> L["cur.next = node, cur = node, node = node.next"]
L --> N{"node?"}
N -- yes --> Q["heapq.heappush(heap, (node.val, i, node))"]
Q --> W
N -- no --> W
```
## Solution
```py
@@ -67,3 +88,7 @@ class Solution:
# Space: O(n)
return D.next
```
## Explanation
<YouTube id="q5a5OiGbT6Q" title="Merge K Sorted Lists - Leetcode 23 - Python" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Heap / Priority Queue</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `points = [[1,3],[-2,2]], k = 1`
@@ -23,6 +23,23 @@ sidebar:
- `1 <= k <= points.length <= 10^4`
- `-10^4 <= xi, yi <= 10^4`
## Approach
```mermaid
flowchart TD
S(["kClosest(points, k)"]) --> I["minHeap = []"]
I --> F{"more x, y in points?"}
F -- yes --> D["dist = x**2 + y**2 — squared distance orders the same as sqrt"]
D --> A["minHeap.append([dist, x, y])"]
A --> F
F -- no --> H["heapq.heapify(minHeap), res = [] — O(n) build, smallest dist on top"]
H --> W{"k > 0?"}
W -- yes --> P["dist, x, y = heapq.heappop(minHeap)"]
P --> R["res.append([x, y]), k -= 1"]
R --> W
W -- no --> E(["return res"])
```
## Solution
```py
@@ -46,3 +63,7 @@ class Solution:
return res
```
## Explanation
<YouTube id="rI2EBUEMfTk" title="K Closest Points to Origin - Heap / Priority Queue - Leetcode 973 - Python" />
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Heap / Priority Queue", order: 14 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Hybrid Structures</Badge>
<Badge variant="accent">Hash Table</Badge>
### Example 1:
- Input: ``
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Hybrid Structures</Badge>
<Badge variant="accent">Linked List</Badge>
### Example 1:
- Input: `l1 = [2,4,3], l2 = [5,6,4]`
@@ -27,6 +27,20 @@ sidebar:
- `0 <= Node.val <= 9`
- It is guaranteed that the list represents a number that does not have leading zeros.
## Approach
```mermaid
flowchart TD
S(["addTwoNumbers(l1, l2)"]) --> I["dummy = ListNode(), carry = 0, curr = dummy — dummy.next will be the head"]
I --> W{"l1 or l2 or carry?"}
W -- no --> E(["return dummy.next"])
W -- yes --> A["s = (l1.val if l1 else 0) + (l2.val if l2 else 0) + carry — a missing digit counts as 0"]
A --> D["carry, val = divmod(s, 10)"]
D --> N["curr.next = ListNode(val), curr = curr.next"]
N --> V["l1 = l1.next if l1 else None, l2 = l2.next if l2 else None"]
V --> W
```
## Solution
```py
@@ -55,3 +69,7 @@ class Solution:
return dummy.next
```
## Explanation
<YouTube id="wgFPrzTjm7s" title="Add Two Numbers - Leetcode 2 - Python" />
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Hybrid Structures", order: 9 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Linked List</Badge>
<Badge variant="accent">Hash Table</Badge>
::::warning
Can you solve it using O(1) (i.e. constant) memory?
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Linked List", order: 8 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Matrix Index Math</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `matrix = [[1,2,3],[4,5,6],[7,8,9]]`
@@ -22,6 +22,22 @@ sidebar:
- `1 <= n <= 20`
- `-1000 <= matrix[i][j] <= 1000`
## Approach
```mermaid
flowchart TD
S(["rotate(matrix)"]) --> N["n = len(matrix)"]
N --> F{"more r in range(n)?"}
F -- yes --> G{"more c in range(r + 1, n)?"}
G -- yes --> T["swap matrix[r][c] and matrix[c][r] — transpose across the diagonal"]
T --> G
G -- no --> F
F -- no --> H{"more r in range(n)?"}
H -- yes --> R["matrix[r].reverse() — transpose then mirror is a clockwise turn"]
R --> H
H -- no --> E(["return None — rotated in place"])
```
## Solution
```py
@@ -35,3 +51,7 @@ class Solution:
for r in range(n):
matrix[r].reverse()
```
## Explanation
<YouTube id="fMSJSS7eO1w" title="Rotate Image - Matrix - Leetcode 48" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Matrix Index Math</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `matrix = [[1,2,3],[4,5,6],[7,8,9]]`
@@ -23,6 +23,24 @@ sidebar:
- `1 <= m, n <= 10`
- `-100 <= matrix[i][j] <= 100`
## Approach
```mermaid
flowchart TD
S(["spiralOrder(matrix)"]) --> I["res = [], top = 0, bottom = len(matrix) - 1, left = 0, right = len(matrix[0]) - 1"]
I --> W{"top <= bottom and left <= right?"}
W -- no --> E(["return res"])
W -- yes --> T["append matrix[top][c] for c in left..right, then top += 1 — top row, shrink from above"]
T --> R["append matrix[r][right] for r in top..bottom, then right -= 1 — right column"]
R --> B{"top <= bottom?"}
B -- yes --> C["append matrix[bottom][c] for c in right down to left, then bottom -= 1 — bottom row"]
C --> L{"left <= right?"}
B -- no --> L
L -- yes --> D["append matrix[r][left] for r in bottom down to top, then left += 1 — left column"]
D --> W
L -- no --> W
```
## Solution
```py
@@ -57,3 +75,7 @@ class Solution:
return res
```
## Explanation
<YouTube id="BJnMZNwUk1M" title="Spiral Matrix - Microsoft Interview Question - Leetcode 54" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Matrix Index Math</Badge>
<Badge variant="accent">Array</Badge>
::::warning
You must do it in place.
@@ -30,6 +30,35 @@ You must do it in place.
- A simple improvement uses O(m + n) space, but still not the best solution.
- Could you devise a constant space solution?
## Approach
```mermaid
flowchart TD
S(["setZeroes(matrix)"]) --> I["m = len(matrix), n = len(matrix[0])"]
I --> Z["row0_zero = any(matrix[0][c] == 0), col0_zero = any(matrix[r][0] == 0) — remember before they become markers"]
Z --> F{"more r in range(1, m)?"}
F -- yes --> G{"more c in range(1, n)?"}
G -- yes --> Q{"matrix[r][c] == 0?"}
Q -- yes --> M["matrix[r][0] = 0, matrix[0][c] = 0 — row 0 and column 0 are the marker lists"]
M --> G
Q -- no --> G
G -- no --> F
F -- no --> H{"more r in range(1, m)?"}
H -- yes --> J{"more c in range(1, n)?"}
J -- yes --> K{"matrix[r][0] == 0 or matrix[0][c] == 0?"}
K -- yes --> W["matrix[r][c] = 0"]
W --> J
K -- no --> J
J -- no --> H
H -- no --> A{"row0_zero?"}
A -- yes --> B["matrix[0][c] = 0 for c in range(n)"]
B --> C{"col0_zero?"}
A -- no --> C
C -- yes --> D["matrix[r][0] = 0 for r in range(m)"]
D --> E(["return None — zeroed in place"])
C -- no --> E
```
## Solution
```py
@@ -65,3 +94,7 @@ class Solution:
for r in range(m):
matrix[r][0] = 0
```
## Explanation
<YouTube id="T41rL0L3Pnw" title="Set Matrix Zeroes - In-place - Leetcode 73" />
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Matrix Index Math", order: 10 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Monotonic Stack</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `temperatures = [73,74,75,71,69,72,76,73]`
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Monotonic Stack</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `target = 12, position = [10,8,0,5,3], speed = [2,4,1,1,3]`
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Monotonic Stack", order: 7 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Prefix Sum</Badge>
<Badge variant="accent">Array</Badge>
::::warning
You must write an algorithm that runs in O(n) time and without using the division operation.
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Prefix Sum</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `["NumArray", "sumRange", "sumRange", "sumRange"]` `[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]`
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Prefix Sum</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `nums = [1,1,1], k = 2`
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Prefix Sum", order: 2 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Sliding Window</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `prices = [7,1,5,3,6,4]`
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Sliding Window</Badge>
<Badge variant="accent">Hash Table</Badge>
### Example 1:
- Input: `s = "abcabcbb"`
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Sliding Window</Badge>
<Badge variant="accent">Hash Table</Badge>
### Example 1:
- Input: `s = "ABAB", k = 2`
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Sliding Window</Badge>
<Badge variant="accent">Hash Table</Badge>
### Example 1:
- Input: `s1 = "ab", s2 = "eidbaooo"`
@@ -1,6 +1,7 @@
---
title: Sliding Window
description: 'Sliding window is a specialized two-pointer family where the pointers define a contiguous interval and you maintain information about that entire interval as it moves.'
hidden: true
---
:::warning
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Sliding Window", order: 4 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Stack</Badge>
<Badge variant="accent">Array</Badge>
### Example 1:
- Input: `tokens = ["2","1","+","3","*"]`
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Stack</Badge>
<Badge variant="accent">String</Badge>
### Example 1:
- Input: `s = "()"`
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Stack", order: 6 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Tree BFS</Badge>
<Badge variant="accent">Tree</Badge>
### Example 1:
- Input: `root = [3,9,20,null,null,15,7]`
@@ -25,6 +25,26 @@ sidebar:
- The number of nodes in the tree is in the range [0, 2000].
- `-1000 <= Node.val <= 1000`
## Approach
```mermaid
flowchart TD
S(["levelOrder(root)"]) --> I["res = [], q = deque(), q.append(root)"]
I --> W{"q non-empty?"}
W -- no --> E(["return res"])
W -- yes --> L["qLen = len(q), level = [] — qLen freezes the current level's size"]
L --> F{"more i in range(qLen)?"}
F -- no --> V{"level non-empty?"}
V -- yes --> A["res.append(level)"]
A --> W
V -- no --> W
F -- yes --> P["node = q.popleft()"]
P --> N{"node is not None?"}
N -- no --> F
N -- yes --> C["level.append(node.val), q.append(node.left), q.append(node.right) — None children are filtered on the next level"]
C --> F
```
## Solution
```py
@@ -59,3 +79,7 @@ class Solution:
return res
```
## Explanation
<YouTube id="6ZnyEApgFYg" title="Binary Tree Level Order Traversal - BFS - Leetcode 102" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Tree BFS</Badge>
<Badge variant="accent">Tree</Badge>
### Example 1:
- Input: `root = [3,1,4,3,null,1,5]`
@@ -28,6 +28,22 @@ sidebar:
- The number of nodes in the binary tree is in the range [1, 10^5].
- Each node's value is between [-10^4, 10^4].
## Approach
```mermaid
flowchart TD
S(["goodNodes(root)"]) --> C["dfs(root, root.val) — the root is always good"]
C --> E(["return that count"])
subgraph dfs["dfs(node, max_val)"]
D1{"node is None?"} -- yes --> D2(["return 0"])
D1 -- no --> D3["is_good = 1 if node.val >= max_val else 0 — max_val is the largest value on the path so far"]
D3 --> D4["new_max = max(max_val, node.val)"]
D4 --> D5["l = dfs(node.left, new_max), r = dfs(node.right, new_max)"]
D5 --> D6(["return is_good + l + r"])
end
C --> dfs
```
## Solution
```py
@@ -50,3 +66,7 @@ class Solution:
return dfs(root, root.val)
```
## Explanation
<YouTube id="7cp5imvDzl4" title="Microsoft's Most Asked Question 2021 - Count Good Nodes in a Binary Tree - Leetcode 1448 - Python" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Tree BFS</Badge>
<Badge variant="accent">Tree</Badge>
### Example 1:
- Input: `root = [1,2,3,null,5,null,4]`
@@ -29,6 +29,26 @@ sidebar:
- The number of nodes in the tree is in the range [0, 100].
- `-100 <= Node.val <= 100`
## Approach
```mermaid
flowchart TD
S(["rightSideView(root)"]) --> I["res = [], q = deque([root])"]
I --> W{"q non-empty?"}
W -- no --> E(["return res"])
W -- yes --> L["rightSide = None, qLen = len(q) — qLen freezes the current level's size"]
L --> F{"more i in range(qLen)?"}
F -- no --> V{"rightSide is not None?"}
V -- yes --> A["res.append(rightSide.val) — the last real node popped on this level is the rightmost"]
A --> W
V -- no --> W
F -- yes --> P["node = q.popleft()"]
P --> N{"node is not None?"}
N -- no --> F
N -- yes --> C["rightSide = node, q.append(node.left), q.append(node.right)"]
C --> F
```
## Solution
```py
@@ -62,3 +82,7 @@ class Solution:
return res
```
## Explanation
<YouTube id="d4zLyf32e3I" title="Binary Tree Right Side View - Breadth First Search - Leetcode 199" />
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Tree BFS", order: 12 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Tree DFS</Badge>
<Badge variant="accent">Tree</Badge>
### Example 1:
- Input: `root = [3,9,20,null,null,15,7]`
@@ -21,6 +21,16 @@ sidebar:
- The number of nodes in the tree is in the range [0, 10^4].
- `-100 <= Node.val <= 100`
## Approach
```mermaid
flowchart TD
S(["maxDepth(root)"]) --> B{"root is None?"}
B -- yes --> Z(["return 0"])
B -- no --> L["l = maxDepth(root.left), r = maxDepth(root.right)"]
L --> E(["return 1 + max(l, r) — this node adds one level above its deeper subtree"])
```
## Solution
```py
@@ -37,3 +47,7 @@ class Solution:
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
```
## Explanation
<YouTube id="hTM3phVI6YQ" title="Maximum Depth of Binary Tree - 3 Solutions - Leetcode 104 - Python" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Tree DFS</Badge>
<Badge variant="accent">Tree</Badge>
### Example 1:
- Input: `root = [4,2,7,1,3,6,9]`
@@ -25,6 +25,17 @@ sidebar:
- The number of nodes in the tree is in the range [0, 100].
- `-100 <= Node.val <= 100`
## Approach
```mermaid
flowchart TD
S(["invertTree(root)"]) --> B{"root is None?"}
B -- yes --> Z(["return None"])
B -- no --> L["left = invertTree(root.left), right = invertTree(root.right) — both subtrees are already inverted"]
L --> X["root.left = right, root.right = left"]
X --> E(["return root"])
```
## Solution
```py
@@ -47,3 +58,7 @@ class Solution:
return root
```
## Explanation
<YouTube id="OnSn2XEQ4MY" title="Invert Binary Tree - Depth First Search - Leetcode 226" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Tree DFS</Badge>
<Badge variant="accent">Tree</Badge>
### Example 1:
- Input: `root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1`
@@ -30,6 +30,21 @@ sidebar:
- `p != q`
- p and q will exist in the tree.
## Approach
```mermaid
flowchart TD
S(["lowestCommonAncestor(root, p, q)"]) --> B{"not root?"}
B -- yes --> N(["return None"])
B -- no --> M{"root == p or root == q?"}
M -- yes --> R(["return root — a node is its own ancestor"])
M -- no --> L["l = self.lowestCommonAncestor(root.left, p, q)"]
L --> Q["r = self.lowestCommonAncestor(root.right, p, q)"]
Q --> D{"l and r?"}
D -- yes --> A(["return root — p and q were found on different sides"])
D -- no --> E(["return l or r — pass up whichever side found something"])
```
## Solution
```py
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Tree DFS", order: 11 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Trie</Badge>
<Badge variant="accent">Hash Table</Badge>
### Example 1:
- Input: ``
@@ -19,6 +19,44 @@ sidebar:
- `word` and `prefix` consist only of lowercase English letters.
- At most 3 * 10^4 calls in total will be made to insert, search, and startsWith.
## Approach
```mermaid
flowchart TD
subgraph state["state — root TrieNode; every node holds a children dict and an endOfWord flag"]
R["root"] --- C["... children[c] ..."] --- L["endOfWord = True — a word ends here"]
end
subgraph insert["insert(word)"]
I1["cur = root"] --> I2{"more c in word?"}
I2 -- yes --> I3{"c not in cur.children?"}
I3 -- yes --> I4["cur.children[c] = TrieNode()"]
I4 --> I5["cur = cur.children[c]"]
I3 -- no --> I5
I5 --> I2
I2 -- no --> I6["cur.endOfWord = True"]
I6 --> I7(["done"])
end
subgraph search["search(word)"]
S1["cur = root"] --> S2{"more c in word?"}
S2 -- yes --> S3{"c not in cur.children?"}
S3 -- yes --> S4(["return False"])
S3 -- no --> S5["cur = cur.children[c]"]
S5 --> S2
S2 -- no --> S6(["return cur.endOfWord — the path exists, but is it a whole word?"])
end
subgraph startsWith["startsWith(prefix)"]
P1["cur = root"] --> P2{"more c in prefix?"}
P2 -- yes --> P3{"c not in cur.children?"}
P3 -- yes --> P4(["return False"])
P3 -- no --> P5["cur = cur.children[c]"]
P5 --> P2
P2 -- no --> P6(["return True — every prefix char had a path"])
end
state --> insert
state --> search
state --> startsWith
```
## Solution
```py
@@ -68,3 +106,7 @@ class Trie:
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)
```
## Explanation
<YouTube id="oobqoCJlHA0" title="Implement Trie (Prefix Tree) - Leetcode 208" />
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Trie</Badge>
<Badge variant="accent">String</Badge>
### Constraints:
@@ -16,6 +16,43 @@ sidebar:
- There will be at most 2 dots in `word` for search queries.
- At most 10^4 calls will be made to addWord and search.
## Approach
```mermaid
flowchart TD
subgraph state["state — root TrieNode; every node holds a children dict and an endOfWord flag"]
R["root"] --- C["... children[c] ..."] --- L["endOfWord = True — a word ends here"]
end
subgraph addWord["addWord(word)"]
A1["cur = root"] --> A2{"more c in word?"}
A2 -- yes --> A3{"c not in cur.children?"}
A3 -- yes --> A4["cur.children[c] = TrieNode()"]
A4 --> A5["cur = cur.children[c]"]
A3 -- no --> A5
A5 --> A2
A2 -- no --> A6["cur.endOfWord = True"]
A6 --> A7(["done"])
end
subgraph search["search(word)"]
S1["dfs(0, root) — match from index 0 at the root"] --> S2["cur = root"]
S2 --> S3{"more i in range(j, len(word))?"}
S3 -- no --> S4(["return cur.endOfWord"])
S3 -- yes --> S5["c = word[i]"]
S5 --> S6{"c == '.'?"}
S6 -- yes --> S7{"more child in cur.children.values()?"}
S7 -- yes --> S8{"dfs(i + 1, child)? — try the rest of the word from this child"}
S8 -- yes --> S9(["return True"])
S8 -- no --> S7
S7 -- no --> S10(["return False — no child matched the wildcard"])
S6 -- no --> S11{"c not in cur.children?"}
S11 -- yes --> S12(["return False"])
S11 -- no --> S13["cur = cur.children[c]"]
S13 --> S3
end
state --> addWord
state --> search
```
## Solution
```py
@@ -61,5 +98,9 @@ class WordDictionary:
# Your WordDictionary object will be instantiated and called as such:
# obj = WordDictionary()
# obj.addWord(word)
# param_2 = obj.search(word)
# param_3 = obj.search(word)
```
## Explanation
<YouTube id="BTf05gs_8iU" title="Design Add and Search Words Data Structure - Leetcode 211 - Python" />
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Trie", order: 15 });
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Two Pointers</Badge>
<Badge variant="accent">Array</Badge>
::::warning
Notice that you may not slant the container.
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Two Pointers</Badge>
<Badge variant="accent">Array</Badge>
::::warning
Notice that the solution set must not contain duplicate triplets.
@@ -6,7 +6,7 @@ sidebar:
badge: 'Medium'
---
<Badge variant="accent">Two Pointers</Badge>
<Badge variant="accent">Array</Badge>
::::warning
Your solution must use only constant extra space.
@@ -6,7 +6,7 @@ sidebar:
badge: 'Easy'
---
<Badge variant="accent">Two Pointers</Badge>
<Badge variant="accent">Array</Badge>
::::warning
Squaring each element and sorting the new array is very trivial, could you find an O(n) solution using a different approach?
+3
View File
@@ -0,0 +1,3 @@
import { defineMeta } from "blume";
export default defineMeta({ title: "Two Pointers", order: 3 });
+1 -1
View File
@@ -8,7 +8,7 @@
"doctor": "blume doctor"
},
"dependencies": {
"blume": "^1.5.1",
"blume": "^1.7.3",
"react": "^19.2.8",
"react-dom": "^19.2.8"
}
+193 -289
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -4,6 +4,7 @@
"type": "module",
"workspaces": ["apps/*"],
"scripts": {
"prepare": "git config core.hooksPath .githooks",
"dev": "cd apps/docs && bun run dev",
"build": "cd apps/docs && bun run build",
"doctor": "cd apps/docs && bun run doctor",
@@ -17,6 +18,7 @@
"pick": "bun apps/cli/pick.ts",
"stats": "bun apps/cli/stats.ts",
"sync": "bun apps/cli/sync.ts",
"videos": "bun apps/cli/videos.ts",
"submit": "bun apps/cli/submit.ts",
"test": "bun apps/cli/test.ts"
}
+93
View File
@@ -0,0 +1,93 @@
"""
127. Word Ladder
Difficulty: Hard
https://leetcode.com/problems/word-ladder/
──────────────────────────────────────────────────
A transformation sequence from word beginWord to word endWord using a
dictionary wordList is a sequence of words beginWord -> s1 -> s2 ->
... -> sk such that:
• Every adjacent pair of words differs by a single letter.
• Every si for 1 <= i <= k is in wordList. Note that beginWord does
not need to be in wordList.
• sk == endWord
Given two words, beginWord and endWord, and a dictionary wordList,
return the number of words in the shortest transformation sequence
from beginWord to endWord, or 0 if no such sequence exists.
Example 1:
Input: beginWord = "hit", endWord = "cog", wordList =
["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: One shortest transformation sequence is "hit" -> "hot"
-> "dot" -> "dog" -> cog", which is 5 words long.
Example 2:
Input: beginWord = "hit", endWord = "cog", wordList =
["hot","dot","dog","lot","log"]
Output: 0
Explanation: The endWord "cog" is not in wordList, therefore there is
no valid transformation sequence.
Constraints:
• 1 <= beginWord.length <= 10
• endWord.length == beginWord.length
• 1 <= wordList.length <= 5000
• wordList[i].length == beginWord.length
• beginWord, endWord, and wordList[i] consist of lowercase English
letters.
• beginWord != endWord
• All the words in wordList are unique.
"""
import collections
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: list[str]) -> int:
if endWord not in wordList:
return 0
adj = collections.defaultdict(list)
wordList.append(beginWord)
for word in wordList:
for j in range(len(word)):
pattern = word[:j] + "*" + word[j + 1 :]
adj[pattern].append(word)
visit = set([beginWord])
q = collections.deque([beginWord])
res = 1
while q:
for i in range(len(q)):
word = q.popleft()
if word == endWord:
return res
for j in range(len(word)):
pattern = word[:j] + "*" + word[j + 1 :]
for adjWord in adj[pattern]:
if adjWord not in visit:
visit.add(adjWord)
q.append(adjWord)
res += 1
return 0
@@ -0,0 +1,72 @@
"""
200. Number of Islands
Difficulty: Medium
https://leetcode.com/problems/number-of-islands/
──────────────────────────────────────────────────
Given an m x n 2D binary grid grid which represents a map of '1's
(land) and '0's (water), return the number of islands.
An island is surrounded by water and is formed by connecting adjacent
lands horizontally or vertically. You may assume all four edges of the
grid are all surrounded by water.
Example 1:
Input: grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
Output: 1
Example 2:
Input: grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
Output: 3
Constraints:
• m == grid.length
• n == grid[i].length
• 1 <= m, n <= 300
• grid[i][j] is '0' or '1'.
"""
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
rows, cols = len(grid), len(grid[0])
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != "1":
return
else:
grid[r][c] = "0"
dfs(r, c + 1) # right
dfs(r + 1, c) # bottom
dfs(r, c - 1) # left
dfs(r - 1, c) # top
num_islands = 0
for r in range(rows):
for c in range(cols):
if grid[r][c] == "1":
num_islands += 1
dfs(r, c)
return num_islands
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff. Show more