mirror of
https://github.com/prdlk/leetcode.git
synced 2026-08-02 17:31:40 +00:00
init(docs): create docs site template with Fumapress and Waku
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.source
|
||||||
|
.vercel
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# docs
|
||||||
|
|
||||||
|
A [Fumapress](https://press.fumadocs.dev) (Fumadocs + Waku) docs site with
|
||||||
|
typography by [shadcn/typeset](https://ui.shadcn.com/docs/typeset) — one CSS
|
||||||
|
file you own.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
Requires **Node.js 22+**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
bun dev # http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Your typeset
|
||||||
|
|
||||||
|
| Control | Choice |
|
||||||
|
| ------- | ------ |
|
||||||
|
| Heading | DM Sans |
|
||||||
|
| Body | Instrument Sans |
|
||||||
|
| Mono | JetBrains Mono |
|
||||||
|
| Size | 16px |
|
||||||
|
| Leading | Loose (1.9) |
|
||||||
|
| Flow | Compact (1em) |
|
||||||
|
| Measure | 80ch |
|
||||||
|
|
||||||
|
- `src/typeset.css` is the stylesheet from the
|
||||||
|
[typeset builder](https://ui.shadcn.com/typeset). It lives in your project —
|
||||||
|
edit it directly, or regenerate it from the builder any time.
|
||||||
|
- `src/app.css` holds your presets. `.typeset-docs` carries the choices
|
||||||
|
above; `.typeset-compact` is a tighter preset for chat-like UI. Rhythm is
|
||||||
|
three variables: `--typeset-size`, `--typeset-leading`, `--typeset-flow`.
|
||||||
|
- `typeset-prompt.md` is an agent-ready prompt with the choices above baked
|
||||||
|
in (like the builder's Prompt tab). Paste it into your coding agent to wire
|
||||||
|
typeset into the surfaces that render markdown.
|
||||||
|
|
||||||
|
Wrap any rendered markdown/HTML:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<article className="typeset typeset-docs typeset-measure">{page}</article>
|
||||||
|
<div className="typeset typeset-compact">{message}</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Opt components out with `not-typeset`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="typeset typeset-docs">
|
||||||
|
<p>Styled prose.</p>
|
||||||
|
<Card className="not-typeset">Untouched component.</Card>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fumadocs prose seam
|
||||||
|
|
||||||
|
Fumadocs UI ships its own `prose` styles for docs page bodies. If you apply
|
||||||
|
`typeset` to the same container, both systems will add spacing. This
|
||||||
|
template zeroes out `margin-block-end` inside `.typeset.prose` (see
|
||||||
|
`src/app.css`) so flow comes only from typeset's `margin-block-start`.
|
||||||
|
|
||||||
|
To apply the typeset classes to docs pages, customize the page layout in
|
||||||
|
`press.config.tsx` — see the
|
||||||
|
[Fumapress layouts docs](https://press.fumadocs.dev/docs/layouts).
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
├── waku.config.ts # Waku/Vite plugins: press(), mdx(), tailwindcss()
|
||||||
|
├── source.config.ts # Fumadocs MDX content source (content/docs)
|
||||||
|
├── press.config.tsx # Site config, adapters, plugins
|
||||||
|
├── typeset-prompt.md # Agent prompt with your typeset picks baked in
|
||||||
|
├── content/docs/ # Your MDX pages
|
||||||
|
└── src/
|
||||||
|
├── app.css # Tailwind + presets + your typeset presets
|
||||||
|
└── typeset.css # shadcn/typeset — yours to edit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploying static
|
||||||
|
|
||||||
|
Fumapress builds static + dynamic routes by default. For a pure CDN deploy,
|
||||||
|
force static mode in `press.config.tsx`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export default defineConfig({ mode: "static", /* ... */ });
|
||||||
|
```
|
||||||
+1732
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: Hello World
|
||||||
|
description: A Fumapress site styled with shadcn/typeset
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This page renders through Fumadocs MDX and is styled by the `typeset typeset-docs` container — headings, paragraphs, lists, tables, and code all inherit their rhythm from three variables: `--typeset-size`, `--typeset-leading`, and `--typeset-flow`.
|
||||||
|
|
||||||
|
### Why one CSS file?
|
||||||
|
|
||||||
|
- The file lives in your repo — edit rules directly, no plugin config
|
||||||
|
- Spacing flows one direction (`margin-block-start` only), so streamed content never restyles earlier blocks
|
||||||
|
- Zero-specificity `:where()` guards mean Tailwind utilities always win
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Inline overrides still work inside a typeset container
|
||||||
|
export const rhythm = {
|
||||||
|
size: "15px",
|
||||||
|
leading: 1.75,
|
||||||
|
flow: "1.5em",
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
> Tune a tighter preset for chat, a roomier one for docs — same file, different rhythm.
|
||||||
|
|
||||||
|
| Control | Purpose | Default |
|
||||||
|
| ------------------ | --------------------- | -------- |
|
||||||
|
| `--typeset-size` | Base text size | `1em` |
|
||||||
|
| `--typeset-leading`| Line height | `1.75` |
|
||||||
|
| `--typeset-flow` | Space between blocks | `1.25em` |
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "docs",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "waku dev",
|
||||||
|
"build": "waku build",
|
||||||
|
"start": "waku start",
|
||||||
|
"types:check": "fumadocs-mdx && tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@base-ui/react": "latest",
|
||||||
|
"@fontsource-variable/dm-sans": "latest",
|
||||||
|
"@fontsource-variable/instrument-sans": "latest",
|
||||||
|
"@fontsource-variable/jetbrains-mono": "latest",
|
||||||
|
"fumadocs-core": "latest",
|
||||||
|
"fumadocs-mdx": "latest",
|
||||||
|
"fumadocs-ui": "npm:@fumadocs/base-ui@latest",
|
||||||
|
"fumapress": "latest",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"react-server-dom-webpack": "^19.2.0",
|
||||||
|
"vercel": "^55.0.0",
|
||||||
|
"waku": "latest"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"tailwindcss": "^4.0.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { defineConfig } from "fumapress";
|
||||||
|
import { fumadocsMdx } from "fumapress/adapters/mdx";
|
||||||
|
import { flexsearchPlugin } from "fumapress/plugins/flexsearch";
|
||||||
|
import { llmsPlugin } from "fumapress/plugins/llms.txt";
|
||||||
|
import { docs } from "./.source/server";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
site: {
|
||||||
|
name: "Docs",
|
||||||
|
baseUrl: import.meta.env.DEV
|
||||||
|
? "http://localhost:3000"
|
||||||
|
: "https://docs.example.com",
|
||||||
|
git: {
|
||||||
|
user: "your-github-user",
|
||||||
|
branch: "main",
|
||||||
|
repo: "docs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
docs: docs.toFumadocsSource(),
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
root() {
|
||||||
|
return <meta property="og:type" content="website" />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.plugins(flexsearchPlugin(), llmsPlugin())
|
||||||
|
.adapters(fumadocsMdx());
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
||||||
|
|
||||||
|
export const docs = defineDocs({
|
||||||
|
dir: "content/docs",
|
||||||
|
docs: {
|
||||||
|
// Required by fumapress' llms.txt plugin (getText("processed")).
|
||||||
|
postprocess: { includeProcessedMarkdown: true },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineConfig();
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
@import "fumadocs-ui/css/neutral.css";
|
||||||
|
@import "fumadocs-ui/css/preset.css";
|
||||||
|
@import "fumapress/css/preset.css";
|
||||||
|
|
||||||
|
/* Fonts — self-hosted via Fontsource, referenced by the typeset presets. */
|
||||||
|
@import "@fontsource-variable/dm-sans";
|
||||||
|
@import "@fontsource-variable/instrument-sans";
|
||||||
|
@import "@fontsource-variable/jetbrains-mono";
|
||||||
|
|
||||||
|
/* shadcn/typeset — one CSS file you own.
|
||||||
|
Regenerate it any time at https://ui.shadcn.com/typeset */
|
||||||
|
@import "./typeset.css";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-dm-sans: "DM Sans Variable", sans-serif;
|
||||||
|
--font-instrument-sans: "Instrument Sans Variable", sans-serif;
|
||||||
|
--font-jetbrains-mono: "JetBrains Mono Variable", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--default-font-family: var(--font-instrument-sans);
|
||||||
|
--font-heading: var(--font-dm-sans);
|
||||||
|
--font-mono: var(--font-jetbrains-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------
|
||||||
|
Typeset presets — tune rhythm per context.
|
||||||
|
Three controls: size, leading, flow.
|
||||||
|
Built with https://ui.shadcn.com/typeset
|
||||||
|
--------------------------------------------------------- */
|
||||||
|
|
||||||
|
.typeset-docs {
|
||||||
|
--typeset-font-body: var(--font-instrument-sans);
|
||||||
|
--typeset-font-heading: var(--font-dm-sans);
|
||||||
|
--typeset-font-mono: var(--font-jetbrains-mono);
|
||||||
|
--typeset-size: 16px;
|
||||||
|
--typeset-leading: 1.9;
|
||||||
|
--typeset-flow: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tighter rhythm, e.g. for changelog callouts or chat-like UI */
|
||||||
|
.typeset-compact {
|
||||||
|
--typeset-leading: 1.6;
|
||||||
|
--typeset-flow: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Measure (80ch) — the builder puts max-width on the wrapper,
|
||||||
|
not in typeset.css. Add this class next to `typeset` when your
|
||||||
|
layout doesn't already constrain line length. */
|
||||||
|
.typeset-measure {
|
||||||
|
max-width: 37em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------
|
||||||
|
Fumadocs interop:
|
||||||
|
Fumadocs UI ships its own `prose` styling for page bodies.
|
||||||
|
When a container is both, let Typeset own the rhythm by
|
||||||
|
disabling the prose margins that would double up.
|
||||||
|
Keep interactive Fumadocs components (tabs, callouts,
|
||||||
|
accordions) out of Typeset with `not-typeset`.
|
||||||
|
--------------------------------------------------------- */
|
||||||
|
.typeset.prose :where(*) {
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,490 @@
|
|||||||
|
/*
|
||||||
|
* shadcn/typeset
|
||||||
|
* https://ui.shadcn.com/docs/typeset.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.typeset {
|
||||||
|
--typeset-font-body: inherit;
|
||||||
|
--typeset-font-heading: var(--font-heading);
|
||||||
|
--typeset-font-mono: var(--font-mono);
|
||||||
|
--typeset-size: 1em;
|
||||||
|
--typeset-leading: 1.75;
|
||||||
|
--typeset-flow: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.typeset {
|
||||||
|
font-family: var(--typeset-font-body);
|
||||||
|
font-size: calc(var(--typeset-size) * 1.125);
|
||||||
|
line-height: var(--typeset-leading);
|
||||||
|
color: var(--color-foreground, currentColor);
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
margin-trim: block-start;
|
||||||
|
|
||||||
|
@media (min-width: 48rem), print {
|
||||||
|
font-size: var(--typeset-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
--typeset-muted: var(
|
||||||
|
--color-muted-foreground,
|
||||||
|
color-mix(in oklab, currentColor 60%, transparent)
|
||||||
|
);
|
||||||
|
--typeset-rule: var(
|
||||||
|
--color-border,
|
||||||
|
color-mix(in oklab, currentColor 20%, transparent)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeset
|
||||||
|
*:not(
|
||||||
|
:where(
|
||||||
|
.not-typeset,
|
||||||
|
[data-not-typeset],
|
||||||
|
.not-typeset *,
|
||||||
|
[data-not-typeset] *
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
/* Paragraphs. */
|
||||||
|
&:where(p) {
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headings. */
|
||||||
|
&:where(h1, h2, h3, h4, h5, h6) {
|
||||||
|
color: var(--color-foreground, currentColor);
|
||||||
|
font-family: var(--typeset-font-heading);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
&:where(h1) {
|
||||||
|
font-size: 1.75em;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
}
|
||||||
|
&:where(h2) {
|
||||||
|
font-size: 1.25em;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-block-start: calc(var(--typeset-flow) * 1.4);
|
||||||
|
}
|
||||||
|
&:where(h3) {
|
||||||
|
font-size: 1.125em;
|
||||||
|
line-height: 1.45;
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
}
|
||||||
|
&:where(h4) {
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
}
|
||||||
|
&:where(h5) {
|
||||||
|
font-size: 0.875em;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--typeset-muted);
|
||||||
|
margin-block-start: calc(var(--typeset-flow) / 0.875);
|
||||||
|
}
|
||||||
|
&:where(h6) {
|
||||||
|
font-size: 0.8125em;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--typeset-muted);
|
||||||
|
margin-block-start: calc(var(--typeset-flow) / 0.8125);
|
||||||
|
}
|
||||||
|
/* Anchors. */
|
||||||
|
&:where([id]) {
|
||||||
|
scroll-margin-block-start: var(--typeset-flow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Headings own the space below them. */
|
||||||
|
&:where(h1 + *, h2 + *, h3 + *, h4 + *, h5 + *, h6 + *) {
|
||||||
|
margin-block-start: 1em;
|
||||||
|
}
|
||||||
|
&:where(:is(h1, h2, h3, h4) :is(code)) {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links. */
|
||||||
|
&:where(a) {
|
||||||
|
color: inherit;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration-line: underline;
|
||||||
|
text-decoration-color: color-mix(in oklab, currentColor 30%, transparent);
|
||||||
|
}
|
||||||
|
&:where(a:hover) {
|
||||||
|
text-decoration-color: currentColor;
|
||||||
|
}
|
||||||
|
&:where(a:focus-visible) {
|
||||||
|
outline: 2px solid var(--color-ring, currentColor);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: 0.125em;
|
||||||
|
}
|
||||||
|
&:where(:is(h1, h2, h3, h4, h5, h6) :is(a)) {
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline semantics. */
|
||||||
|
&:where(strong, b) {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
&:where(:is(h1, h2, h3, h4) :is(strong, b)) {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
&:where(mark) {
|
||||||
|
background-color: color-mix(
|
||||||
|
in oklab,
|
||||||
|
oklch(0.86 0.17 95) 40%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
color: inherit;
|
||||||
|
padding: 0.05em 0.15em;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
}
|
||||||
|
&:where(abbr[title]) {
|
||||||
|
text-decoration-line: underline;
|
||||||
|
text-decoration-style: dotted;
|
||||||
|
text-underline-offset: 0.2em;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
&:where(del, s) {
|
||||||
|
color: var(--typeset-muted);
|
||||||
|
text-decoration-line: line-through;
|
||||||
|
}
|
||||||
|
&:where(sup, sub) {
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
&:where(sup) {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
&:where(sub) {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
&:where(sup a) {
|
||||||
|
text-decoration-line: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lists. */
|
||||||
|
&:where(ul) {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
&:where(ol) {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
&:where(ul ul) {
|
||||||
|
list-style-type: circle;
|
||||||
|
}
|
||||||
|
&:where(ul ul ul) {
|
||||||
|
list-style-type: square;
|
||||||
|
}
|
||||||
|
&:where(ol[type="a" i]) {
|
||||||
|
list-style-type: lower-alpha;
|
||||||
|
}
|
||||||
|
&:where(ol[type="i" i]) {
|
||||||
|
list-style-type: lower-roman;
|
||||||
|
}
|
||||||
|
&:where(ol[type="A" s]) {
|
||||||
|
list-style-type: upper-alpha;
|
||||||
|
}
|
||||||
|
&:where(ol[type="I" s]) {
|
||||||
|
list-style-type: upper-roman;
|
||||||
|
}
|
||||||
|
&:where(ul, ol) {
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
margin-block-end: 0;
|
||||||
|
padding-inline-start: 1.5em;
|
||||||
|
}
|
||||||
|
&:where(li) {
|
||||||
|
margin-block-start: 0.5em;
|
||||||
|
padding-inline-start: 0.4em;
|
||||||
|
}
|
||||||
|
&:where(li > p, li > ul, li > ol) {
|
||||||
|
margin-block-start: 0.5em;
|
||||||
|
}
|
||||||
|
&:where(ul > li)::marker {
|
||||||
|
color: var(--typeset-muted);
|
||||||
|
}
|
||||||
|
&:where(ol > li)::marker {
|
||||||
|
color: var(--typeset-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GFM task lists. */
|
||||||
|
&:where(ul.contains-task-list) {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-inline-start: 0.25em;
|
||||||
|
}
|
||||||
|
&:where(li.task-list-item > input[type="checkbox"]) {
|
||||||
|
margin-inline-end: 0.5em;
|
||||||
|
vertical-align: -0.1em;
|
||||||
|
accent-color: var(--color-primary, currentColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disclosures. */
|
||||||
|
&:where(details) {
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
&:where(summary) {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
&:where(summary)::marker {
|
||||||
|
color: var(--typeset-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyboard keys. */
|
||||||
|
&:where(kbd) {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid var(--typeset-rule);
|
||||||
|
border-block-end-width: 2px;
|
||||||
|
border-radius: min(calc(var(--radius, 0.5em) * 0.6), 0.35em);
|
||||||
|
padding: 0.0625em 0.35em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Definition lists. */
|
||||||
|
&:where(dl) {
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
&:where(dt) {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-block-start: 1em;
|
||||||
|
}
|
||||||
|
&:where(dt + dt) {
|
||||||
|
margin-block-start: 0.25em;
|
||||||
|
}
|
||||||
|
&:where(dd) {
|
||||||
|
margin-block-start: 0.25em;
|
||||||
|
margin-inline-start: 0;
|
||||||
|
padding-inline-start: 1em;
|
||||||
|
color: var(--typeset-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code. */
|
||||||
|
&:where(:not(pre) > code) {
|
||||||
|
background-color: var(
|
||||||
|
--color-muted,
|
||||||
|
color-mix(in oklab, currentColor 8%, transparent)
|
||||||
|
);
|
||||||
|
font-family: var(--typeset-font-mono);
|
||||||
|
font-size: 0.85em;
|
||||||
|
border-radius: min(calc(var(--radius, 0.5em) * 0.6), 0.35em);
|
||||||
|
padding: 0.125em 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks. */
|
||||||
|
&:where(pre) {
|
||||||
|
background-color: var(
|
||||||
|
--color-muted,
|
||||||
|
color-mix(in oklab, currentColor 8%, transparent)
|
||||||
|
);
|
||||||
|
font-family: var(--typeset-font-mono);
|
||||||
|
font-size: 0.875em;
|
||||||
|
line-height: 1.5;
|
||||||
|
tab-size: 2;
|
||||||
|
direction: ltr;
|
||||||
|
border-radius: var(--radius, 0.5em);
|
||||||
|
padding: 0.75em 1em;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-block-start: calc(var(--typeset-flow) / 0.875);
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
&:where(pre code) {
|
||||||
|
background-color: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blockquote. */
|
||||||
|
&:where(blockquote) {
|
||||||
|
border-inline-start: 2px solid var(--typeset-rule);
|
||||||
|
padding-inline-start: 1em;
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
margin-block-end: 0;
|
||||||
|
margin-inline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dividers. */
|
||||||
|
&:where(hr) {
|
||||||
|
border: 0;
|
||||||
|
border-block-start: 1px solid var(--typeset-rule);
|
||||||
|
margin-block-start: calc(var(--typeset-flow) * 2.4);
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
&:where(hr + h1, hr + h2, hr + h3, hr + h4) {
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GFM footnotes. */
|
||||||
|
&:where(.footnotes, [data-footnotes]) {
|
||||||
|
margin-block-start: calc(var(--typeset-flow) * 2);
|
||||||
|
border-block-start: 1px solid var(--typeset-rule);
|
||||||
|
padding-block-start: var(--typeset-flow);
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: var(--typeset-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Math. */
|
||||||
|
&:where(math[display="block"]) {
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
margin-block-end: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding-block: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Media. */
|
||||||
|
&:where(img, video) {
|
||||||
|
border-radius: var(--radius, 0.5em);
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
&:where(p img) {
|
||||||
|
margin-block-start: 0;
|
||||||
|
}
|
||||||
|
&:where(figure) {
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
margin-block-end: 0;
|
||||||
|
margin-inline: 0;
|
||||||
|
}
|
||||||
|
&:where(figcaption, caption) {
|
||||||
|
color: var(--typeset-muted);
|
||||||
|
font-size: 0.875em;
|
||||||
|
text-align: center;
|
||||||
|
margin-block-start: calc(0.75em / 0.875);
|
||||||
|
}
|
||||||
|
&:where(caption) {
|
||||||
|
caption-side: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables. Separators sit on cells, not tr:last-child: append-safe.
|
||||||
|
Bare tables stay real tables (keep their semantics) and wrap to fit. */
|
||||||
|
&:where(table) {
|
||||||
|
max-width: 100%;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
border-block-end: 1px solid var(--typeset-rule);
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Horizontal scroll for any wide block. Wrap it and the wrapper owns the
|
||||||
|
flow margin. Tables shrink to fit, so widen the table to make it scroll
|
||||||
|
instead of compress. */
|
||||||
|
&:where(.typeset-scroll) {
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-block-start: var(--typeset-flow);
|
||||||
|
}
|
||||||
|
&:where(.typeset-scroll > *) {
|
||||||
|
margin-block-start: 0;
|
||||||
|
}
|
||||||
|
&:where(.typeset-scroll table) {
|
||||||
|
width: max-content;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
&:where(thead th) {
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: start;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0.65em 1em;
|
||||||
|
}
|
||||||
|
&:where(:is(tbody, tfoot) :is(td, th)) {
|
||||||
|
padding: 0.75em 1em;
|
||||||
|
text-align: start;
|
||||||
|
vertical-align: top;
|
||||||
|
border-block-start: 1px solid var(--typeset-rule);
|
||||||
|
}
|
||||||
|
&:where(tbody th, tfoot th) {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
&:where(th:first-child, td:first-child) {
|
||||||
|
padding-inline-start: 0;
|
||||||
|
}
|
||||||
|
&:where(th[align="center"], td[align="center"]) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
&:where(th[align="right"], td[align="right"]) {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blocks inside list items keep the list's tighter rhythm. */
|
||||||
|
&:where(li > blockquote, li > table, li > figure) {
|
||||||
|
margin-block-start: 0.5em;
|
||||||
|
}
|
||||||
|
&:where(li > pre) {
|
||||||
|
margin-block-start: calc(0.5em / 0.875);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
&:where(pre, table, blockquote, figure) {
|
||||||
|
break-inside: avoid;
|
||||||
|
}
|
||||||
|
&:where(h1, h2, h3, h4) {
|
||||||
|
break-after: avoid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forced colors strips the code background, so give it a border. */
|
||||||
|
@media (forced-colors: active) {
|
||||||
|
&:where(:not(pre) > code) {
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First child adds no space above. Last so it wins ties. */
|
||||||
|
.typeset
|
||||||
|
> :where(:first-child):not(
|
||||||
|
:where(
|
||||||
|
.not-typeset,
|
||||||
|
[data-not-typeset],
|
||||||
|
.not-typeset *,
|
||||||
|
[data-not-typeset] *
|
||||||
|
)
|
||||||
|
),
|
||||||
|
.typeset
|
||||||
|
> :where(:first-child)
|
||||||
|
> :where(:first-child):not(
|
||||||
|
:where(
|
||||||
|
.not-typeset,
|
||||||
|
[data-not-typeset],
|
||||||
|
.not-typeset *,
|
||||||
|
[data-not-typeset] *
|
||||||
|
)
|
||||||
|
),
|
||||||
|
.typeset
|
||||||
|
:where(
|
||||||
|
li > :first-child,
|
||||||
|
blockquote > :first-child,
|
||||||
|
td > :first-child,
|
||||||
|
th > :first-child,
|
||||||
|
dd > :first-child,
|
||||||
|
figure > :first-child,
|
||||||
|
figure > picture > img
|
||||||
|
):not(
|
||||||
|
:where(
|
||||||
|
.not-typeset,
|
||||||
|
[data-not-typeset],
|
||||||
|
.not-typeset *,
|
||||||
|
[data-not-typeset] *
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
margin-block-start: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"types": ["react/experimental"],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src", "press.config.tsx", "source.config.ts", ".source"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
Install shadcn/typeset in this Fumapress (Fumadocs + Waku) project.
|
||||||
|
|
||||||
|
Typeset is a single stylesheet that styles rendered markdown: wrap the output
|
||||||
|
in a `typeset` container and everything inside (headings, lists, tables,
|
||||||
|
code, blockquotes, math) is styled. Everything outside is untouched.
|
||||||
|
|
||||||
|
If this project was scaffolded with create-fumapress-typeset, steps 1-4 are
|
||||||
|
already done — verify each one and move on; every step below is safe to
|
||||||
|
re-run.
|
||||||
|
|
||||||
|
1. Download https://ui.shadcn.com/typeset.css and save it as
|
||||||
|
`src/typeset.css` (next to the main CSS file, `src/app.css`). If the
|
||||||
|
file already exists, replace it with the downloaded copy.
|
||||||
|
|
||||||
|
2. Import it in `src/app.css`, after the Tailwind and Fumadocs imports:
|
||||||
|
|
||||||
|
@import "./typeset.css";
|
||||||
|
|
||||||
|
3. Install the fonts:
|
||||||
|
|
||||||
|
bun add @fontsource-variable/dm-sans @fontsource-variable/instrument-sans @fontsource-variable/jetbrains-mono
|
||||||
|
|
||||||
|
Then import them in `src/app.css`:
|
||||||
|
|
||||||
|
@import "@fontsource-variable/dm-sans";
|
||||||
|
@import "@fontsource-variable/instrument-sans";
|
||||||
|
@import "@fontsource-variable/jetbrains-mono";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-dm-sans: "DM Sans Variable", sans-serif;
|
||||||
|
--font-instrument-sans: "Instrument Sans Variable", sans-serif;
|
||||||
|
--font-jetbrains-mono: "JetBrains Mono Variable", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
4. Add this preset to `src/app.css`, after the typeset import. If a class
|
||||||
|
named `.typeset-docs` already exists, update its values in place. Leave
|
||||||
|
any other `typeset-*` presets untouched: they are separate surfaces:
|
||||||
|
|
||||||
|
.typeset-docs {
|
||||||
|
--typeset-font-body: var(--font-instrument-sans);
|
||||||
|
--typeset-font-heading: var(--font-dm-sans);
|
||||||
|
--typeset-font-mono: var(--font-jetbrains-mono);
|
||||||
|
--typeset-size: 16px;
|
||||||
|
--typeset-leading: 1.9;
|
||||||
|
--typeset-flow: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
5. Do not apply the class anywhere yet. Search the project for surfaces that
|
||||||
|
render markdown or rich content:
|
||||||
|
|
||||||
|
- The Fumadocs docs page body. Fumapress owns that layout: customize it in
|
||||||
|
`press.config.tsx` with `createDocsLayoutPage` from
|
||||||
|
"fumapress/layouts/docs" (see https://press.fumadocs.dev/docs/layouts)
|
||||||
|
and wrap the page body there.
|
||||||
|
- MDX components, react-markdown/Streamdown renderers,
|
||||||
|
dangerouslySetInnerHTML with parsed markdown, `prose` classes, CMS
|
||||||
|
content renderers.
|
||||||
|
|
||||||
|
Present the candidates you find as a short list and ask the user which
|
||||||
|
surface should use typeset. Then wrap only the surface they pick:
|
||||||
|
|
||||||
|
<div className="typeset typeset-docs max-w-[37em]">
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
(`src/app.css` also defines `.typeset-measure` with the same
|
||||||
|
max-width, if a plain class is preferred over a Tailwind utility.)
|
||||||
|
|
||||||
|
If the picked surface already has its own typography (Fumadocs' `prose`
|
||||||
|
class, styled markdown components), list those styles and let the user
|
||||||
|
decide what to remove before wrapping. Note: `src/app.css` already zeroes
|
||||||
|
`margin-block-end` inside `.typeset.prose` so typeset owns the rhythm
|
||||||
|
when both classes share a container.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- To exclude an embedded component from typeset styles, add the
|
||||||
|
`not-typeset` class or the `data-not-typeset` attribute to it. Keep
|
||||||
|
interactive Fumadocs components (tabs, callouts, accordions) out of typeset
|
||||||
|
this way.
|
||||||
|
- Verify on the surface the user picked: headings, lists, tables, and code
|
||||||
|
inside the container should be styled with no classes on the content
|
||||||
|
itself.
|
||||||
|
- Docs: https://ui.shadcn.com/docs/typeset
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineConfig } from "waku/config";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import press from "fumapress/vite";
|
||||||
|
import mdx from "fumadocs-mdx/vite";
|
||||||
|
|
||||||
|
// Config file for Waku & Vite
|
||||||
|
export default defineConfig({
|
||||||
|
vite: {
|
||||||
|
plugins: [press(), mdx(), tailwindcss()],
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# 1365. How Many Numbers Are Smaller Than the Current Number
|
||||||
|
|
||||||
|
**Difficulty:** Easy
|
||||||
|
**URL:** https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/
|
||||||
|
**Topics:** Array, Hash Table, Sorting, Counting Sort
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The One Insight That Makes This Work
|
||||||
|
|
||||||
|
> If I know how many times each value appears, and I add those counts up from left to right, then `prefix[v]` tells me **how many elements are ≤ v** — instantly, for any v.
|
||||||
|
|
||||||
|
That's it. Everything below is just executing this idea.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Spot the Signal
|
||||||
|
|
||||||
|
Read the constraints:
|
||||||
|
|
||||||
|
```
|
||||||
|
0 <= nums[i] <= 100
|
||||||
|
```
|
||||||
|
|
||||||
|
Values are bounded to a tiny range (0–100). This is the **flashing neon sign** that says: don't sort, don't nest loops — build a frequency array indexed by value.
|
||||||
|
|
||||||
|
**Rule of thumb:** value range ≤ ~10⁶ and you need counting/ranking? Frequency array.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Count Every Value (the "bucket" pass)
|
||||||
|
|
||||||
|
Make an array with one slot per possible value. Walk the input once. Each number votes for its own slot.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const freq = new Array(101).fill(0); // slots for values 0..100
|
||||||
|
for (const x of nums) freq[x]++;
|
||||||
|
```
|
||||||
|
|
||||||
|
For `nums = [8, 1, 2, 2, 3]`:
|
||||||
|
|
||||||
|
```
|
||||||
|
value: 0 1 2 3 4 5 6 7 8 ...
|
||||||
|
freq: 0 1 2 1 0 0 0 0 1 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Read it as: "one 1, two 2s, one 3, one 8."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Prefix Sum (the magic pass)
|
||||||
|
|
||||||
|
Now transform `freq` in place: each slot becomes itself **plus everything before it**.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
for (let i = 1; i < 101; i++) freq[i] += freq[i - 1];
|
||||||
|
```
|
||||||
|
|
||||||
|
Same example after the pass:
|
||||||
|
|
||||||
|
```
|
||||||
|
value: 0 1 2 3 4 5 6 7 8 ...
|
||||||
|
freq: 0 1 3 4 4 4 4 4 5 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
New meaning: `freq[v]` = **count of elements ≤ v**.
|
||||||
|
|
||||||
|
- `freq[3] = 4` → four numbers are ≤ 3 (they are 1, 2, 2, 3) ✓
|
||||||
|
- `freq[7] = 4` → still four numbers ≤ 7 ✓
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Answer Queries in O(1)
|
||||||
|
|
||||||
|
"How many numbers are **strictly smaller** than x?" is the same question as "how many numbers are **≤ x − 1**?"
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
return nums.map((x) => (x === 0 ? 0 : freq[x - 1]));
|
||||||
|
```
|
||||||
|
|
||||||
|
The `x === 0` guard exists because nothing can be smaller than the minimum possible value — and `freq[-1]` would be `undefined`.
|
||||||
|
|
||||||
|
Trace on `[8, 1, 2, 2, 3]`:
|
||||||
|
|
||||||
|
| x | lookup | answer |
|
||||||
|
|---|--------|--------|
|
||||||
|
| 8 | freq[7] | 4 |
|
||||||
|
| 1 | freq[0] | 0 |
|
||||||
|
| 2 | freq[1] | 1 |
|
||||||
|
| 2 | freq[1] | 1 |
|
||||||
|
| 3 | freq[2] | 3 |
|
||||||
|
|
||||||
|
→ `[4, 0, 1, 1, 3]` ✓
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Full Solution
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var smallerNumbersThanCurrent = function (nums) {
|
||||||
|
// 1. Bucket counts
|
||||||
|
const freq = new Array(101).fill(0);
|
||||||
|
for (const x of nums) freq[x]++;
|
||||||
|
|
||||||
|
// 2. Prefix sum: freq[v] = count of elements <= v
|
||||||
|
for (let i = 1; i < 101; i++) freq[i] += freq[i - 1];
|
||||||
|
|
||||||
|
// 3. Strictly smaller than x == count of elements <= x-1
|
||||||
|
return nums.map((x) => (x === 0 ? 0 : freq[x - 1]));
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Complexity:** O(n + k) time, O(k) space, where k = value range (101 here). No sort, no log factor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Reusable Pattern (memorize this shape)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. BUCKET — freq[value]++ for every element
|
||||||
|
2. PREFIX — freq[i] += freq[i-1] left to right
|
||||||
|
3. QUERY — freq[v] answers "how many ≤ v" in O(1)
|
||||||
|
freq[v-1] answers "how many < v"
|
||||||
|
n - freq[v] answers "how many > v"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Where else this exact shape shows up
|
||||||
|
|
||||||
|
| Problem | Same pattern, different query |
|
||||||
|
|---|---|
|
||||||
|
| **Counting Sort** | Prefix sums become final sorted positions |
|
||||||
|
| **LC 315 / rank queries** | "How many smaller" is literally a rank |
|
||||||
|
| **LC 1122 Relative Sort Array** | Bucket + walk buckets in order |
|
||||||
|
| **Radix sort digit pass** | Bucket by digit, prefix for placement |
|
||||||
|
| **Histogram percentiles** | freq[v] / n = percentile of v |
|
||||||
|
| **"How many in range [a, b]?"** | freq[b] − freq[a−1] — the prefix subtraction trick |
|
||||||
|
|
||||||
|
### The generalization ladder
|
||||||
|
|
||||||
|
- Values bounded and small → **frequency array** (this pattern)
|
||||||
|
- Values huge but few distinct → **coordinate compression** first, then this pattern
|
||||||
|
- Need updates between queries → upgrade prefix array to a **Fenwick tree (BIT)** — same idea, log-time updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
1. **Returning `freq[x]` instead of `freq[x-1]`** — that counts elements ≤ x (including x itself and its duplicates). Off-by-one between "≤" and "<" is where this pattern bites.
|
||||||
|
2. **Forgetting the `x === 0` edge** — smallest possible value has nothing below it.
|
||||||
|
3. **Sizing the array to `nums.length` instead of the value range** — the buckets are indexed by *value*, not position.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 1365. How Many Numbers Are Smaller Than the Current Number
|
||||||
|
* Difficulty: Easy
|
||||||
|
* https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/
|
||||||
|
*
|
||||||
|
* ──────────────────────────────────────────────────
|
||||||
|
*
|
||||||
|
* Given the array nums, for each nums[i] find out how many numbers in
|
||||||
|
* the array are smaller than it. That is, for each nums[i] you have to
|
||||||
|
* count the number of valid j's such that j != i and nums[j] < nums[i].
|
||||||
|
*
|
||||||
|
* Return the answer in an array.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Example 1:
|
||||||
|
*
|
||||||
|
* Input: nums = [8,1,2,2,3]
|
||||||
|
* Output: [4,0,1,1,3]
|
||||||
|
* Explanation:
|
||||||
|
* For nums[0]=8 there exist four smaller numbers than it (1, 2, 2 and
|
||||||
|
* 3).
|
||||||
|
* For nums[1]=1 does not exist any smaller number than it.
|
||||||
|
* For nums[2]=2 there exist one smaller number than it (1).
|
||||||
|
* For nums[3]=2 there exist one smaller number than it (1).
|
||||||
|
* For nums[4]=3 there exist three smaller numbers than it (1, 2 and 2).
|
||||||
|
*
|
||||||
|
* Example 2:
|
||||||
|
*
|
||||||
|
* Input: nums = [6,5,4,8]
|
||||||
|
* Output: [2,1,0,3]
|
||||||
|
*
|
||||||
|
* Example 3:
|
||||||
|
*
|
||||||
|
* Input: nums = [7,7,7,7]
|
||||||
|
* Output: [0,0,0,0]
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Constraints:
|
||||||
|
*
|
||||||
|
* • 2 <= nums.length <= 500
|
||||||
|
*
|
||||||
|
* • 0 <= nums[i] <= 100
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var smallerNumbersThanCurrent = function (nums) {
|
||||||
|
// Step 1: Begin by initializing a [Frequency Map]()
|
||||||
|
const freq = {};
|
||||||
|
for (let n of nums) freq[n] = (freq[n] || 0) + 1;
|
||||||
|
|
||||||
|
// Step 2: Sort the numbers by ascending order
|
||||||
|
const sorted = Object.keys(freq).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
// Step 3: Init a count of numbers smaller than the active number
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
// Step 4: Init a map to track number of values smaller for each number
|
||||||
|
const smaller = {};
|
||||||
|
|
||||||
|
// Step 5: Iterate over the sorted list
|
||||||
|
for (let num of sorted) {
|
||||||
|
// Set count for active number
|
||||||
|
smaller[num] = count;
|
||||||
|
|
||||||
|
// Update the count by frequency
|
||||||
|
count += freq[num];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6: Use original list and find number of smaller values than it
|
||||||
|
return nums.map((n) => smaller[n]);
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 1636. Sort Array by Increasing Frequency
|
||||||
|
* Difficulty: Easy
|
||||||
|
* https://leetcode.com/problems/sort-array-by-increasing-frequency/
|
||||||
|
*
|
||||||
|
* ──────────────────────────────────────────────────
|
||||||
|
*
|
||||||
|
* Given an array of integers nums, sort the array in increasing order
|
||||||
|
* based on the frequency of the values. If multiple values have the same
|
||||||
|
* frequency, sort them in decreasing order.
|
||||||
|
*
|
||||||
|
* Return the sorted array.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Example 1:
|
||||||
|
*
|
||||||
|
* Input: nums = [1,1,2,2,2,3]
|
||||||
|
* Output: [3,1,1,2,2,2]
|
||||||
|
* Explanation: '3' has a frequency of 1, '1' has a frequency of 2, and
|
||||||
|
* '2' has a frequency of 3.
|
||||||
|
*
|
||||||
|
* Example 2:
|
||||||
|
*
|
||||||
|
* Input: nums = [2,3,1,3,2]
|
||||||
|
* Output: [1,3,3,2,2]
|
||||||
|
* Explanation: '2' and '3' both have a frequency of 2, so they are
|
||||||
|
* sorted in decreasing order.
|
||||||
|
*
|
||||||
|
* Example 3:
|
||||||
|
*
|
||||||
|
* Input: nums = [-1,1,-6,4,5,-6,1,4,1]
|
||||||
|
* Output: [5,-1,4,4,-6,-6,1,1,1]
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Constraints:
|
||||||
|
*
|
||||||
|
* • 1 <= nums.length <= 100
|
||||||
|
*
|
||||||
|
* • -100 <= nums[i] <= 100
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
var frequencySort = function (nums) {
|
||||||
|
const freq = {};
|
||||||
|
for (let n of nums) freq[n] = (freq[n] || 0) + 1;
|
||||||
|
return nums.sort((a, b) => freq[a] - freq[b] || b - a);
|
||||||
|
};
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* 387. First Unique Character in a String
|
||||||
|
* Difficulty: Easy
|
||||||
|
* https://leetcode.com/problems/first-unique-character-in-a-string/
|
||||||
|
*
|
||||||
|
* ──────────────────────────────────────────────────
|
||||||
|
*
|
||||||
|
* Given a string s, find the first non-repeating character in it and
|
||||||
|
* return its index. If it does not exist, return -1.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Example 1:
|
||||||
|
*
|
||||||
|
* Input: s = "leetcode"
|
||||||
|
*
|
||||||
|
* Output: 0
|
||||||
|
*
|
||||||
|
* Explanation:
|
||||||
|
*
|
||||||
|
* The character 'l' at index 0 is the first character that does not
|
||||||
|
* occur at any other index.
|
||||||
|
*
|
||||||
|
* Example 2:
|
||||||
|
*
|
||||||
|
* Input: s = "loveleetcode"
|
||||||
|
*
|
||||||
|
* Output: 2
|
||||||
|
*
|
||||||
|
* Example 3:
|
||||||
|
*
|
||||||
|
* Input: s = "aabb"
|
||||||
|
*
|
||||||
|
* Output: -1
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Constraints:
|
||||||
|
*
|
||||||
|
* • 1 <= s.length <= 10^5
|
||||||
|
*
|
||||||
|
* • s consists of only lowercase English letters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} s
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
var firstUniqChar = function (s) {
|
||||||
|
const freq = {};
|
||||||
|
for (let c of s) freq[c] = (freq[c] || 0) + 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
if (freq[s[i]] === 1) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 347. Top K Frequent Elements
|
||||||
|
* Difficulty: Medium
|
||||||
|
* https://leetcode.com/problems/top-k-frequent-elements/
|
||||||
|
*
|
||||||
|
* ──────────────────────────────────────────────────
|
||||||
|
*
|
||||||
|
* Given an integer array nums and an integer k, return the k most
|
||||||
|
* frequent elements. You may return the answer in any order.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Example 1:
|
||||||
|
*
|
||||||
|
* Input: nums = [1,1,1,2,2,3], k = 2
|
||||||
|
*
|
||||||
|
* Output: [1,2]
|
||||||
|
*
|
||||||
|
* Example 2:
|
||||||
|
*
|
||||||
|
* Input: nums = [1], k = 1
|
||||||
|
*
|
||||||
|
* Output: [1]
|
||||||
|
*
|
||||||
|
* Example 3:
|
||||||
|
*
|
||||||
|
* Input: nums = [1,2,1,2,1,2,3,1,3,2], k = 2
|
||||||
|
*
|
||||||
|
* Output: [1,2]
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Constraints:
|
||||||
|
*
|
||||||
|
* • 1 <= nums.length <= 10^5
|
||||||
|
*
|
||||||
|
* • -10^4 <= nums[i] <= 10^4
|
||||||
|
*
|
||||||
|
* • k is in the range [1, the number of unique elements in the array].
|
||||||
|
*
|
||||||
|
* • It is guaranteed that the answer is unique.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Follow up: Your algorithm's time complexity must be better than O(n
|
||||||
|
* log n), where n is the array's size.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} k
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
var topKFrequent = function (nums, k) {
|
||||||
|
const freq = {};
|
||||||
|
for (let n of nums) freq[n] = (freq[n] || 0) + 1;
|
||||||
|
|
||||||
|
return Object.keys(freq)
|
||||||
|
.map(Number)
|
||||||
|
.sort((a, b) => freq[b] - freq[a])
|
||||||
|
.slice(0, k);
|
||||||
|
};
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 451. Sort Characters By Frequency
|
||||||
|
* Difficulty: Medium
|
||||||
|
* https://leetcode.com/problems/sort-characters-by-frequency/
|
||||||
|
*
|
||||||
|
* ──────────────────────────────────────────────────
|
||||||
|
*
|
||||||
|
* Given a string s, sort it in decreasing order based on the frequency
|
||||||
|
* of the characters. The frequency of a character is the number of times
|
||||||
|
* it appears in the string.
|
||||||
|
*
|
||||||
|
* Return the sorted string. If there are multiple answers, return any
|
||||||
|
* of them.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Example 1:
|
||||||
|
*
|
||||||
|
* Input: s = "tree"
|
||||||
|
* Output: "eert"
|
||||||
|
* Explanation: 'e' appears twice while 'r' and 't' both appear once.
|
||||||
|
* So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also
|
||||||
|
* a valid answer.
|
||||||
|
*
|
||||||
|
* Example 2:
|
||||||
|
*
|
||||||
|
* Input: s = "cccaaa"
|
||||||
|
* Output: "aaaccc"
|
||||||
|
* Explanation: Both 'c' and 'a' appear three times, so both "cccaaa"
|
||||||
|
* and "aaaccc" are valid answers.
|
||||||
|
* Note that "cacaca" is incorrect, as the same characters must be
|
||||||
|
* together.
|
||||||
|
*
|
||||||
|
* Example 3:
|
||||||
|
*
|
||||||
|
* Input: s = "Aabb"
|
||||||
|
* Output: "bbAa"
|
||||||
|
* Explanation: "bbaA" is also a valid answer, but "Aabb" is incorrect.
|
||||||
|
* Note that 'A' and 'a' are treated as two different characters.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Constraints:
|
||||||
|
*
|
||||||
|
* • 1 <= s.length <= 5 * 10^5
|
||||||
|
*
|
||||||
|
* • s consists of uppercase and lowercase English letters and digits.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} s
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
var frequencySort = function (s) {
|
||||||
|
// count the frequency of each character
|
||||||
|
const freq = {};
|
||||||
|
for (let c of s) freq[c] = (freq[c] || 0) + 1;
|
||||||
|
|
||||||
|
// sort the characters by frequency
|
||||||
|
return s
|
||||||
|
.split("")
|
||||||
|
.sort((a, b) => freq[b] - freq[a] || a.localeCompare(b))
|
||||||
|
.join("");
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user