mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 13:12:44 +00:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26085cc027 | ||
|
|
216152d946 | ||
|
|
d0ace85bbc | ||
|
|
5b99a6a589 | ||
|
|
8446623607 | ||
|
|
3a1df27d30 | ||
|
|
ae1d1ceede | ||
|
|
f5f31ba1c3 | ||
|
|
d1c99c307e | ||
|
|
021b1fd06e | ||
|
|
edd37ed0e6 | ||
|
|
c2a8a31ede | ||
|
|
891a1f0c5a | ||
|
|
2e5d991d47 | ||
|
|
73d63c2565 | ||
|
|
cafc8a4c25 | ||
|
|
7c5061dcd1 | ||
|
|
f9e69960c4 | ||
|
|
0c12df3eba | ||
|
|
bfb53c14e8 | ||
|
|
2c30b3d017 | ||
|
|
1084410832 | ||
|
|
76c9c77307 | ||
|
|
23eb71d387 | ||
|
|
b8c779903f | ||
|
|
5059eb72cb |
No files matched your search
@@ -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.
|
||||||
Executable
+18
@@ -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
|
||||||
@@ -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 |
|
| `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/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) |
|
| `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) |
|
| `.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
|
## 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
|
bun run submit # submit ONE solution to LeetCode (newest scaffold first, nothing
|
||||||
# hidden; last_submitted stamped in the db on exit 0)
|
# 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 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 dev|build # Blume docs site (runs in apps/docs/)
|
||||||
bun run close-solved -- --dry-run # issue reconcilers (also DRY_RUN=1)
|
bun run close-solved -- --dry-run # issue reconcilers (also DRY_RUN=1)
|
||||||
bun run close-topics -- --dry-run
|
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/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/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/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/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.
|
- `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`).
|
- `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`).
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://x.com/basedprad"><img alt="follow" src="https://shieldcn.dev/x/follow/basedprad.svg" /></a>
|
<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"><img alt="badge" src="https://shieldcn.dev/badge/Read-Documentation.svg?logo=lu%3ABook" /></a>
|
||||||
<a href="https://lc.prad.nu/progress"><img alt="gate pass rate" src="https://srs-api.prdlk.workers.dev/badge/gate.svg" /></a>
|
<a href="https://lc.prad.nu/progress"><img alt="gate pass rate" src="https://srs-api.prdlk.workers.dev/badge/gate.svg" /></a>
|
||||||
</p>
|
</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">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 align="center"><code>B</code> ⭐</td><td align="center">502</td><td><a href="https://leetcode.com/problems/ipo/">IPO</a></td></tr>
|
||||||
<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>
|
<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>
|
||||||
<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 & Search Words</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 & Search Words</a></td></tr>
|
||||||
@@ -234,7 +235,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<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>
|
<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>
|
||||||
<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>
|
<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">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 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>
|
<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>
|
<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>
|
||||||
<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>
|
<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">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 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>
|
<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>
|
<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>
|
||||||
<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>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">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 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>
|
<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>
|
<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>
|
||||||
<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>
|
<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>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<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>
|
<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>
|
||||||
<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>
|
<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">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 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>
|
<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>
|
<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>
|
||||||
<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>
|
<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">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 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>
|
<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>
|
<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>
|
||||||
<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>
|
<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">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 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>
|
<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>
|
<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>
|
||||||
<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>
|
<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">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 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>
|
<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>
|
<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>
|
||||||
<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>
|
<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>
|
<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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -8,12 +8,13 @@
|
|||||||
"test": "bun ./test.ts",
|
"test": "bun ./test.ts",
|
||||||
"submit": "bun ./submit.ts",
|
"submit": "bun ./submit.ts",
|
||||||
"sync": "bun ./sync.ts",
|
"sync": "bun ./sync.ts",
|
||||||
|
"videos": "bun ./videos.ts",
|
||||||
"close-solved": "bun ./close-solved.ts",
|
"close-solved": "bun ./close-solved.ts",
|
||||||
"close-topics": "bun ./close-topics.ts"
|
"close-topics": "bun ./close-topics.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@clack/prompts": "^1.7.0",
|
"@clack/prompts": "^1.7.0",
|
||||||
"@night-slayer18/leetcode-cli": "^3.5.0",
|
"@night-slayer18/leetcode-cli": "^3.5.2",
|
||||||
"@types/bun": "^1.3.14"
|
"@types/bun": "^1.3.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+31
-17
@@ -30,17 +30,27 @@ function lcNumber(rel: string): number | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Fuzzy-pick a solution under work/ and run `leetcode <command>` on it. */
|
/** 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 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
|
// register creation times so files older than the db still sort correctly
|
||||||
for (const f of scanned) {
|
for (const f of scanned) {
|
||||||
const num = lcNumber(f);
|
const num = lcNumber(f);
|
||||||
if (num === undefined) continue;
|
if (num === undefined) continue;
|
||||||
const stat = statSync(join(WORK_DIR, f));
|
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();
|
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;
|
return (command === "test" ? row?.lastTested : row?.createdAt) ?? undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const files = scanned
|
const files = scanned.sort((a, b) => {
|
||||||
.sort((a, b) => {
|
const ka = sortKey(a);
|
||||||
const ka = sortKey(a);
|
const kb = sortKey(b);
|
||||||
const kb = sortKey(b);
|
if (ka !== kb) {
|
||||||
if (ka !== kb) {
|
if (ka === undefined) return 1;
|
||||||
if (ka === undefined) return 1;
|
if (kb === undefined) return -1;
|
||||||
if (kb === undefined) return -1;
|
return kb.localeCompare(ka); // ISO timestamps: lexicographic = chronological
|
||||||
return kb.localeCompare(ka); // ISO timestamps: lexicographic = chronological
|
}
|
||||||
}
|
return a.localeCompare(b, undefined, { numeric: true });
|
||||||
return a.localeCompare(b, undefined, { numeric: true });
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (files.length === 0) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +85,14 @@ export async function runOnSolution(command: "test" | "submit", message: string)
|
|||||||
maxItems: 12,
|
maxItems: 12,
|
||||||
options: files.map((f) => {
|
options: files.map((f) => {
|
||||||
const parsed = parseWorkPath(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 {
|
return {
|
||||||
value: f,
|
value: f,
|
||||||
label: basename(f),
|
label: basename(f),
|
||||||
hint: parsed ? `${stage} · ${parsed.difficulty} · ${parsed.category}` : f,
|
hint: parsed
|
||||||
|
? `${stage} · ${parsed.difficulty} · ${parsed.category}`
|
||||||
|
: f,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
filter: (search, option) => {
|
filter: (search, option) => {
|
||||||
|
|||||||
+73
-17
@@ -4,18 +4,31 @@
|
|||||||
*
|
*
|
||||||
* - New problems get a full page (frontmatter, badge, warning, examples,
|
* - New problems get a full page (frontmatter, badge, warning, examples,
|
||||||
* constraints, solution) in the gold-standard format.
|
* constraints, solution) in the gold-standard format.
|
||||||
* - Already-ported pages only get their `## Solution` section regenerated,
|
* - Already-ported pages only get their script-owned parts regenerated — the
|
||||||
* so hand-curated prose is never touched.
|
* `<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
|
* - When a problem is solved in both JavaScript and Python the solution
|
||||||
* renders as a <CodeGroup> (Python first); single-language solutions
|
* renders as a <CodeGroup> (Python first); single-language solutions
|
||||||
* render as a plain fence.
|
* 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
|
* 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
|
* of problems whose page already exists (see work.ts), and a fresh scaffold
|
||||||
* there would otherwise wipe the page's `## Solution`.
|
* there would otherwise wipe the page's `## Solution`.
|
||||||
*/
|
*/
|
||||||
import { mkdir, rename } from "node:fs/promises";
|
import { mkdir, readdir, rename, rmdir, unlink } from "node:fs/promises";
|
||||||
import { basename, join } from "node:path";
|
import { basename, dirname, join } from "node:path";
|
||||||
|
|
||||||
const ROOT = join(import.meta.dir, "..", "..");
|
const ROOT = join(import.meta.dir, "..", "..");
|
||||||
const WORK = join(ROOT, "work", "1");
|
const WORK = join(ROOT, "work", "1");
|
||||||
@@ -158,24 +171,37 @@ function parseHeader(header: string): Header {
|
|||||||
|
|
||||||
// ── README topic map ─────────────────────────────────────────────
|
// ── 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> = {
|
const TOPIC_ALIASES: Record<string, string> = {
|
||||||
"Hash-Based Lookup": "Hash Table",
|
"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 md = await Bun.file(join(ROOT, "README.md")).text();
|
||||||
const map = new Map<number, string>();
|
const map = new Map<number, Topic>();
|
||||||
let topic = "";
|
let topic: Topic | null = null;
|
||||||
for (const line of md.split("\n")) {
|
for (const line of md.split("\n")) {
|
||||||
const t = line.match(/<strong>\d+\.\s*([^<]+)<\/strong>/);
|
const t = line.match(/<strong>(\d+)\.\s*([^<]+)<\/strong>/);
|
||||||
if (t) topic = TOPIC_ALIASES[t[1]!.trim()] ?? t[1]!.trim();
|
if (t) topic = { order: Number(t[1]), name: TOPIC_ALIASES[t[2]!.trim()] ?? t[2]!.trim() };
|
||||||
if (!topic) continue;
|
if (!topic) continue;
|
||||||
const n = line.match(/<td align="center">(\d+)<\/td>/);
|
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;
|
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 ────────────────────────────
|
// ── inline-code formatting heuristics ────────────────────────────
|
||||||
|
|
||||||
/** Identifiers worth backticking in prose, harvested from the code. */
|
/** 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 h = sol.header;
|
||||||
const ids = identifiers(sol);
|
const ids = identifiers(sol);
|
||||||
const description = h.statement[0]?.replace(/\.\s*$/, "") ?? "";
|
const description = h.statement[0]?.replace(/\.\s*$/, "") ?? "";
|
||||||
@@ -292,7 +322,7 @@ function renderPage(sol: Solution, topic: string): string {
|
|||||||
` badge: '${h.difficulty}'`,
|
` 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 report: [string, string, string][] = [];
|
||||||
const ordered = [...solutions.values()].sort((a, b) => a.num - b.num);
|
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) {
|
for (const sol of ordered) {
|
||||||
const langs = Object.keys(sol.code).sort().join("+");
|
const langs = Object.keys(sol.code).sort().join("+");
|
||||||
const workRef = `${sol.num}.${sol.slug} (${langs})`;
|
const workRef = `${sol.num}.${sol.slug} (${langs})`;
|
||||||
// (category) group folder + leetcode-number prefix: numeric sidebar order, both stripped from the URL.
|
// <topic>/ folder (a real route segment, so its index.mdx is the topic landing
|
||||||
const group = `(${sol.category.toLowerCase().replaceAll(" ", "-")})`;
|
// 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 name = `${group}/${sol.num}-${sol.slug}.mdx`;
|
||||||
const existing = pages.get(sol.num);
|
const existing = pages.get(sol.num);
|
||||||
await mkdir(join(DOCS, group), { recursive: true });
|
await mkdir(join(DOCS, group), { recursive: true });
|
||||||
@@ -406,10 +443,11 @@ for (const sol of ordered) {
|
|||||||
}
|
}
|
||||||
const tail = page.slice(solutionEnd(page, at));
|
const tail = page.slice(solutionEnd(page, at));
|
||||||
const body = page.slice(0, at) + solutionSection(sol) + (tail ? `\n${tail}` : "");
|
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[] = [];
|
const actions: string[] = [];
|
||||||
if (existing !== name) {
|
if (existing !== name) {
|
||||||
await rename(join(DOCS, existing), join(DOCS, name));
|
await rename(join(DOCS, existing), join(DOCS, name));
|
||||||
|
vacated.add(dirname(existing));
|
||||||
actions.push("moved");
|
actions.push("moved");
|
||||||
}
|
}
|
||||||
if (next !== page) {
|
if (next !== page) {
|
||||||
@@ -418,12 +456,30 @@ for (const sol of ordered) {
|
|||||||
}
|
}
|
||||||
report.push([workRef, name, actions.join(" + ") || "skipped (in sync)"]);
|
report.push([workRef, name, actions.join(" + ") || "skipped (in sync)"]);
|
||||||
} else {
|
} else {
|
||||||
const topic = topics.get(sol.num) ?? sol.category;
|
await Bun.write(join(DOCS, name), renderPage(sol));
|
||||||
await Bun.write(join(DOCS, name), renderPage(sol, topic));
|
|
||||||
report.push([workRef, name, "created"]);
|
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 w0 = Math.max(...report.map((r) => r[0].length), 9);
|
||||||
const w1 = Math.max(...report.map((r) => r[1].length), 9);
|
const w1 = Math.max(...report.map((r) => r[1].length), 9);
|
||||||
console.log(`${"work file".padEnd(w0)} ${"docs page".padEnd(w1)} status`);
|
console.log(`${"work file".padEnd(w0)} ${"docs page".padEnd(w1)} status`);
|
||||||
|
|||||||
Executable
+173
@@ -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` : ""}`);
|
||||||
@@ -10,12 +10,4 @@ export default defineConfig({
|
|||||||
content: {
|
content: {
|
||||||
root: "content",
|
root: "content",
|
||||||
},
|
},
|
||||||
navigation: {
|
|
||||||
tabs: [
|
|
||||||
{
|
|
||||||
label: "Reference",
|
|
||||||
path: "/reference",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+25
@@ -27,6 +27,27 @@ Could you write a generalized algorithm to work on any possible set of character
|
|||||||
- `0 <= strs[i].length <= 200`
|
- `0 <= strs[i].length <= 200`
|
||||||
- `strs[i]` contains any possible characters out of 256 valid ASCII characters.
|
- `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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -46,3 +67,7 @@ class Codec:
|
|||||||
# codec = Codec()
|
# codec = Codec()
|
||||||
# codec.decode(codec.encode(strs))
|
# codec.decode(codec.encode(strs))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="B1k_sxOSgv8" title="Encode and Decode Strings - Leetcode 271 - Python" />
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Array" });
|
||||||
+22
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Binary Search Tree</Badge>
|
<Badge variant="accent">Tree</Badge>
|
||||||
|
|
||||||
::::warning
|
::::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?
|
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`
|
- `1 <= k <= n <= 10^4`
|
||||||
- `0 <= Node.val <= 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -52,3 +69,7 @@ class Solution:
|
|||||||
return cur.val
|
return cur.val
|
||||||
cur = cur.right
|
cur = cur.right
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="5LUXSvjmGCw" title="Kth Smallest Element in a BST - Leetcode 230 - Python" />
|
||||||
+21
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Binary Search Tree</Badge>
|
<Badge variant="accent">Tree</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8`
|
- 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 != q`
|
||||||
- p and q will exist in the BST.
|
- 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -54,3 +70,7 @@ class Solution:
|
|||||||
else:
|
else:
|
||||||
return cur
|
return cur
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="gs2LMfuOR9k" title="Lowest Common Ancestor of a Binary Search Tree - Leetcode 235 - Python" />
|
||||||
+22
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Binary Search Tree</Badge>
|
<Badge variant="accent">Tree</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `root = [2,1,3]`
|
- Input: `root = [2,1,3]`
|
||||||
@@ -22,6 +22,23 @@ sidebar:
|
|||||||
- The number of nodes in the tree is in the range [1, 10^4].
|
- The number of nodes in the tree is in the range [1, 10^4].
|
||||||
- `-2^31 <= Node.val <= 2^31 - 1`
|
- `-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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -44,3 +61,7 @@ class Solution:
|
|||||||
|
|
||||||
return valid(root, float("-inf"), float("inf"))
|
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 });
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Binary Search</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::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]].
|
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]].
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Binary Search</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
You must write an algorithm with O(log n) runtime complexity.
|
You must write an algorithm with O(log n) runtime complexity.
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Binary Search</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
You must write an algorithm with O(log n) runtime complexity.
|
You must write an algorithm with O(log n) runtime complexity.
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Binary Search</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
You must write an algorithm with O(log n) runtime complexity.
|
You must write an algorithm with O(log n) runtime complexity.
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Binary Search</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
You must write a solution in O(log(m * n)) time complexity.
|
You must write a solution in O(log(m * n)) time complexity.
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Binary Search</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `piles = [3,6,7,11], h = 8`
|
- Input: `piles = [3,6,7,11], h = 8`
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Graph DFS", order: 16 });
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Hash Table</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
Can you come up with an algorithm that is less than O(n^2) time complexity?
|
Can you come up with an algorithm that is less than O(n^2) time complexity?
|
||||||
File renamed without changes.
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Hash Table</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `nums = [1,2,3,1]`
|
- Input: `nums = [1,2,3,1]`
|
||||||
File renamed without changes.
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Hash Table</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
Your algorithm's time complexity must be better than O(n log n), where n is the array's size.
|
Your algorithm's time complexity must be better than O(n log n), where n is the array's size.
|
||||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Hash Table</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `strs = ["eat","tea","tan","ate","nat","bat"]`
|
- Input: `strs = ["eat","tea","tan","ate","nat","bat"]`
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Hash Table", order: 1 });
|
||||||
+19
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Heap / Priority Queue</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `nums = [3,2,1,5,6,4], k = 2`
|
- Input: `nums = [3,2,1,5,6,4], k = 2`
|
||||||
@@ -21,6 +21,20 @@ sidebar:
|
|||||||
- `1 <= k <= nums.length <= 10^5`
|
- `1 <= k <= nums.length <= 10^5`
|
||||||
- `-10^4 <= nums[i] <= 10^4`
|
- `-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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -39,3 +53,7 @@ class Solution:
|
|||||||
|
|
||||||
return -heapq.heappop(nums)
|
return -heapq.heappop(nums)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="XEmy13g1Qxc" title="Kth Largest Element in an Array - Quick Select - Leetcode 215 - Python" />
|
||||||
+26
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Hard'
|
badge: 'Hard'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Heap / Priority Queue</Badge>
|
<Badge variant="accent">Linked List</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `lists = [[1,4,5],[1,3,4],[2,6]]`
|
- Input: `lists = [[1,4,5],[1,3,4],[2,6]]`
|
||||||
@@ -30,6 +30,27 @@ sidebar:
|
|||||||
- `lists[i]` is sorted in ascending order.
|
- `lists[i]` is sorted in ascending order.
|
||||||
- The sum of lists[i].length will not exceed 10^4.
|
- 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -67,3 +88,7 @@ class Solution:
|
|||||||
# Space: O(n)
|
# Space: O(n)
|
||||||
return D.next
|
return D.next
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="q5a5OiGbT6Q" title="Merge K Sorted Lists - Leetcode 23 - Python" />
|
||||||
+22
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Heap / Priority Queue</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `points = [[1,3],[-2,2]], k = 1`
|
- Input: `points = [[1,3],[-2,2]], k = 1`
|
||||||
@@ -23,6 +23,23 @@ sidebar:
|
|||||||
- `1 <= k <= points.length <= 10^4`
|
- `1 <= k <= points.length <= 10^4`
|
||||||
- `-10^4 <= xi, yi <= 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -46,3 +63,7 @@ class Solution:
|
|||||||
|
|
||||||
return res
|
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 });
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Hybrid Structures</Badge>
|
<Badge variant="accent">Hash Table</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: ``
|
- Input: ``
|
||||||
+19
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Hybrid Structures</Badge>
|
<Badge variant="accent">Linked List</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `l1 = [2,4,3], l2 = [5,6,4]`
|
- Input: `l1 = [2,4,3], l2 = [5,6,4]`
|
||||||
@@ -27,6 +27,20 @@ sidebar:
|
|||||||
- `0 <= Node.val <= 9`
|
- `0 <= Node.val <= 9`
|
||||||
- It is guaranteed that the list represents a number that does not have leading zeros.
|
- 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -55,3 +69,7 @@ class Solution:
|
|||||||
|
|
||||||
return dummy.next
|
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 });
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Linked List</Badge>
|
<Badge variant="accent">Hash Table</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
Can you solve it using O(1) (i.e. constant) memory?
|
Can you solve it using O(1) (i.e. constant) memory?
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Linked List", order: 8 });
|
||||||
+21
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Matrix Index Math</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `matrix = [[1,2,3],[4,5,6],[7,8,9]]`
|
- Input: `matrix = [[1,2,3],[4,5,6],[7,8,9]]`
|
||||||
@@ -22,6 +22,22 @@ sidebar:
|
|||||||
- `1 <= n <= 20`
|
- `1 <= n <= 20`
|
||||||
- `-1000 <= matrix[i][j] <= 1000`
|
- `-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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -35,3 +51,7 @@ class Solution:
|
|||||||
for r in range(n):
|
for r in range(n):
|
||||||
matrix[r].reverse()
|
matrix[r].reverse()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="fMSJSS7eO1w" title="Rotate Image - Matrix - Leetcode 48" />
|
||||||
+23
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Matrix Index Math</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `matrix = [[1,2,3],[4,5,6],[7,8,9]]`
|
- Input: `matrix = [[1,2,3],[4,5,6],[7,8,9]]`
|
||||||
@@ -23,6 +23,24 @@ sidebar:
|
|||||||
- `1 <= m, n <= 10`
|
- `1 <= m, n <= 10`
|
||||||
- `-100 <= matrix[i][j] <= 100`
|
- `-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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -57,3 +75,7 @@ class Solution:
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="BJnMZNwUk1M" title="Spiral Matrix - Microsoft Interview Question - Leetcode 54" />
|
||||||
+34
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Matrix Index Math</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
You must do it in place.
|
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.
|
- A simple improvement uses O(m + n) space, but still not the best solution.
|
||||||
- Could you devise a constant space 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -65,3 +94,7 @@ class Solution:
|
|||||||
for r in range(m):
|
for r in range(m):
|
||||||
matrix[r][0] = 0
|
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 });
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Monotonic Stack</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `temperatures = [73,74,75,71,69,72,76,73]`
|
- Input: `temperatures = [73,74,75,71,69,72,76,73]`
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Monotonic Stack</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `target = 12, position = [10,8,0,5,3], speed = [2,4,1,1,3]`
|
- 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 });
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Prefix Sum</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
You must write an algorithm that runs in O(n) time and without using the division operation.
|
You must write an algorithm that runs in O(n) time and without using the division operation.
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Prefix Sum</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `["NumArray", "sumRange", "sumRange", "sumRange"]` `[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]`
|
- Input: `["NumArray", "sumRange", "sumRange", "sumRange"]` `[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]`
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Prefix Sum</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `nums = [1,1,1], k = 2`
|
- Input: `nums = [1,1,1], k = 2`
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Prefix Sum", order: 2 });
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Sliding Window</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `prices = [7,1,5,3,6,4]`
|
- Input: `prices = [7,1,5,3,6,4]`
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Sliding Window</Badge>
|
<Badge variant="accent">Hash Table</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `s = "abcabcbb"`
|
- Input: `s = "abcabcbb"`
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Sliding Window</Badge>
|
<Badge variant="accent">Hash Table</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `s = "ABAB", k = 2`
|
- Input: `s = "ABAB", k = 2`
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Sliding Window</Badge>
|
<Badge variant="accent">Hash Table</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `s1 = "ab", s2 = "eidbaooo"`
|
- Input: `s1 = "ab", s2 = "eidbaooo"`
|
||||||
+1
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Sliding Window
|
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.'
|
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
|
:::warning
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Sliding Window", order: 4 });
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Stack</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `tokens = ["2","1","+","3","*"]`
|
- Input: `tokens = ["2","1","+","3","*"]`
|
||||||
File renamed without changes.
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Stack</Badge>
|
<Badge variant="accent">String</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `s = "()"`
|
- Input: `s = "()"`
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Stack", order: 6 });
|
||||||
+25
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Tree BFS</Badge>
|
<Badge variant="accent">Tree</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `root = [3,9,20,null,null,15,7]`
|
- 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].
|
- The number of nodes in the tree is in the range [0, 2000].
|
||||||
- `-1000 <= Node.val <= 1000`
|
- `-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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -59,3 +79,7 @@ class Solution:
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="6ZnyEApgFYg" title="Binary Tree Level Order Traversal - BFS - Leetcode 102" />
|
||||||
+21
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Tree BFS</Badge>
|
<Badge variant="accent">Tree</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `root = [3,1,4,3,null,1,5]`
|
- 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].
|
- 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].
|
- 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -50,3 +66,7 @@ class Solution:
|
|||||||
|
|
||||||
return dfs(root, root.val)
|
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" />
|
||||||
+25
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Tree BFS</Badge>
|
<Badge variant="accent">Tree</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `root = [1,2,3,null,5,null,4]`
|
- 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].
|
- The number of nodes in the tree is in the range [0, 100].
|
||||||
- `-100 <= Node.val <= 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -62,3 +82,7 @@ class Solution:
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="d4zLyf32e3I" title="Binary Tree Right Side View - Breadth First Search - Leetcode 199" />
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Tree BFS", order: 12 });
|
||||||
+15
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Tree DFS</Badge>
|
<Badge variant="accent">Tree</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `root = [3,9,20,null,null,15,7]`
|
- 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].
|
- The number of nodes in the tree is in the range [0, 10^4].
|
||||||
- `-100 <= Node.val <= 100`
|
- `-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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -37,3 +47,7 @@ class Solution:
|
|||||||
|
|
||||||
return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
|
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" />
|
||||||
+16
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Tree DFS</Badge>
|
<Badge variant="accent">Tree</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `root = [4,2,7,1,3,6,9]`
|
- 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].
|
- The number of nodes in the tree is in the range [0, 100].
|
||||||
- `-100 <= Node.val <= 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -47,3 +58,7 @@ class Solution:
|
|||||||
|
|
||||||
return root
|
return root
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="OnSn2XEQ4MY" title="Invert Binary Tree - Depth First Search - Leetcode 226" />
|
||||||
+16
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Tree DFS</Badge>
|
<Badge variant="accent">Tree</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: `root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 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 != q`
|
||||||
- p and q will exist in the tree.
|
- 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Tree DFS", order: 11 });
|
||||||
+43
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Trie</Badge>
|
<Badge variant="accent">Hash Table</Badge>
|
||||||
|
|
||||||
### Example 1:
|
### Example 1:
|
||||||
- Input: ``
|
- Input: ``
|
||||||
@@ -19,6 +19,44 @@ sidebar:
|
|||||||
- `word` and `prefix` consist only of lowercase English letters.
|
- `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.
|
- 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -68,3 +106,7 @@ class Trie:
|
|||||||
# param_2 = obj.search(word)
|
# param_2 = obj.search(word)
|
||||||
# param_3 = obj.startsWith(prefix)
|
# param_3 = obj.startsWith(prefix)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
<YouTube id="oobqoCJlHA0" title="Implement Trie (Prefix Tree) - Leetcode 208" />
|
||||||
+43
-2
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Trie</Badge>
|
<Badge variant="accent">String</Badge>
|
||||||
|
|
||||||
### Constraints:
|
### Constraints:
|
||||||
|
|
||||||
@@ -16,6 +16,43 @@ sidebar:
|
|||||||
- There will be at most 2 dots in `word` for search queries.
|
- There will be at most 2 dots in `word` for search queries.
|
||||||
- At most 10^4 calls will be made to addWord and search.
|
- 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
|
## Solution
|
||||||
|
|
||||||
```py
|
```py
|
||||||
@@ -61,5 +98,9 @@ class WordDictionary:
|
|||||||
# Your WordDictionary object will be instantiated and called as such:
|
# Your WordDictionary object will be instantiated and called as such:
|
||||||
# obj = WordDictionary()
|
# obj = WordDictionary()
|
||||||
# obj.addWord(word)
|
# 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" />
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Trie", order: 15 });
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Two Pointers</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
Notice that you may not slant the container.
|
Notice that you may not slant the container.
|
||||||
File renamed without changes.
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Two Pointers</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
Notice that the solution set must not contain duplicate triplets.
|
Notice that the solution set must not contain duplicate triplets.
|
||||||
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Medium'
|
badge: 'Medium'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Two Pointers</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
Your solution must use only constant extra space.
|
Your solution must use only constant extra space.
|
||||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -6,7 +6,7 @@ sidebar:
|
|||||||
badge: 'Easy'
|
badge: 'Easy'
|
||||||
---
|
---
|
||||||
|
|
||||||
<Badge variant="accent">Two Pointers</Badge>
|
<Badge variant="accent">Array</Badge>
|
||||||
|
|
||||||
::::warning
|
::::warning
|
||||||
Squaring each element and sorting the new array is very trivial, could you find an O(n) solution using a different approach?
|
Squaring each element and sorting the new array is very trivial, could you find an O(n) solution using a different approach?
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { defineMeta } from "blume";
|
||||||
|
|
||||||
|
export default defineMeta({ title: "Two Pointers", order: 3 });
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"doctor": "blume doctor"
|
"doctor": "blume doctor"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"blume": "^1.5.1",
|
"blume": "^1.7.3",
|
||||||
"react": "^19.2.8",
|
"react": "^19.2.8",
|
||||||
"react-dom": "^19.2.8"
|
"react-dom": "^19.2.8"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": ["apps/*"],
|
"workspaces": ["apps/*"],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"prepare": "git config core.hooksPath .githooks",
|
||||||
"dev": "cd apps/docs && bun run dev",
|
"dev": "cd apps/docs && bun run dev",
|
||||||
"build": "cd apps/docs && bun run build",
|
"build": "cd apps/docs && bun run build",
|
||||||
"doctor": "cd apps/docs && bun run doctor",
|
"doctor": "cd apps/docs && bun run doctor",
|
||||||
@@ -17,6 +18,7 @@
|
|||||||
"pick": "bun apps/cli/pick.ts",
|
"pick": "bun apps/cli/pick.ts",
|
||||||
"stats": "bun apps/cli/stats.ts",
|
"stats": "bun apps/cli/stats.ts",
|
||||||
"sync": "bun apps/cli/sync.ts",
|
"sync": "bun apps/cli/sync.ts",
|
||||||
|
"videos": "bun apps/cli/videos.ts",
|
||||||
"submit": "bun apps/cli/submit.ts",
|
"submit": "bun apps/cli/submit.ts",
|
||||||
"test": "bun apps/cli/test.ts"
|
"test": "bun apps/cli/test.ts"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
Reference in new issue
Block a user