From 5774cac8c9c548d7c7e96d33324eb085e66bad19 Mon Sep 17 00:00:00 2001 From: Sohan Rout Date: Fri, 11 Sep 2026 11:27:37 +0530 Subject: [PATCH] Fix : fixed graphs --- app/components/ui/graph.jsx | 191 +++++++++++---- app/components/ui/graphChart.jsx | 222 ------------------ .../amortized-analysis/content.jsx | 73 +++--- .../fundamentals/amortized-analysis/page.jsx | 8 +- .../basics/asymptotic-notation/content.jsx | 87 ++++--- .../basics/asymptotic-notation/page.jsx | 12 +- .../basics/time-space-complexity/code.js | 4 +- .../basics/time-space-complexity/content.jsx | 69 +++--- .../basics/time-space-complexity/page.jsx | 10 +- .../master-theorem/code.js | 2 +- .../master-theorem/content.jsx | 63 ++--- .../master-theorem/page.jsx | 8 +- .../recursion-tree/content.jsx | 51 ++-- .../recursion-tree/page.jsx | 6 +- .../substitution-method/content.jsx | 73 +++--- .../substitution-method/page.jsx | 12 +- app/design-algorithm/layout.jsx | 2 +- bun.lock | 75 ------ package.json | 1 - 19 files changed, 411 insertions(+), 558 deletions(-) delete mode 100755 app/components/ui/graphChart.jsx diff --git a/app/components/ui/graph.jsx b/app/components/ui/graph.jsx index e864c68..99dc16b 100755 --- a/app/components/ui/graph.jsx +++ b/app/components/ui/graph.jsx @@ -1,51 +1,148 @@ -"use client"; - -import { useEffect, useRef, useState } from "react"; -import dynamic from "next/dynamic"; - -// recharts is ~370 KiB and the graph sits far below the fold, so it is code -// split and only fetched once the placeholder scrolls near the viewport. -const ComplexityChart = dynamic(() => import("./graphChart"), { - ssr: false, - loading: () => null, -}); - -const ComplexityGraph = (props) => { - const ref = useRef(null); - const [visible, setVisible] = useState(false); - - useEffect(() => { - const node = ref.current; - if (!node) return; - - if (typeof IntersectionObserver === "undefined") { - setVisible(true); - return; - } - - const observer = new IntersectionObserver( - (entries) => { - if (entries.some((entry) => entry.isIntersecting)) { - setVisible(true); - observer.disconnect(); - } - }, - { rootMargin: "300px" } - ); - - observer.observe(node); - return () => observer.disconnect(); - }, []); - - // Reserve the same box the chart occupies so nothing shifts when it loads. +/** + * Complexity curves, drawn as inline SVG in the same visual language as the + * BoundDiagram on the design-and-analysis pages: thin slate axes, no grid, + * 2.5px rounded strokes and a colour key underneath. + * + * This replaced a recharts LineChart. recharts was ~370 KiB and had to be code + * split and lazy-loaded behind an IntersectionObserver to keep it off the + * critical path; a hand-drawn plot costs nothing, needs no placeholder box to + * avoid layout shift, and server-renders, so the graph is in the HTML rather + * than appearing after hydration. + * + * The prop shape is unchanged from the recharts version so the 49 module call + * sites did not have to be touched. + */ + +// Legend order is the order a reader expects: best, average, worst. +const CURVES = [ + { key: "bestCase", label: "Best Case", color: "#10b981", dashed: false }, + { key: "averageCase", label: "Average Case", color: "#3b82f6", dashed: true }, + { key: "worstCase", label: "Worst Case", color: "#ef4444", dashed: false }, +]; + +// Paint order is not legend order. Curves coincide exactly in most modules — +// bubble sort's average and worst are both n², linear search's best and average +// are both n — and whichever is painted last wins. Average is therefore drawn +// last and dashed, so it stays visible whichever of the other two it lies on +// top of, and the solid line showing through its gaps identifies that one. +const PAINT_ORDER = ["worstCase", "bestCase", "averageCase"]; + +const W = 480; +const H = 260; +const PAD_L = 44; +const PAD_R = 16; +const PAD_T = 18; +const PAD_B = 38; +const PLOT_W = W - PAD_L - PAD_R; +const PLOT_H = H - PAD_T - PAD_B; +const SAMPLES = 80; + +const ComplexityGraph = ({ + bestCase = null, + averageCase = null, + worstCase = null, + maxN = 100, + title = "Time Complexity Analysis", +}) => { + const fns = { bestCase, averageCase, worstCase }; + const active = CURVES.filter((c) => typeof fns[c.key] === "function"); + + // Sample from n = 1: several modules plot log2(n), which is -Infinity at 0. + const span = Math.max(1, maxN - 1); + const ns = Array.from( + { length: SAMPLES + 1 }, + (_, i) => 1 + (i / SAMPLES) * span + ); + + const valuesOf = (fn) => ns.map((n) => fn(n)).map((v) => (Number.isFinite(v) ? v : null)); + const series = active.map((c) => ({ ...c, values: valuesOf(fns[c.key]) })); + + const yMax = Math.max( + 1e-9, + ...series.flatMap((s) => s.values.filter((v) => v !== null)) + ); + + const toX = (n) => PAD_L + ((n - 1) / span) * PLOT_W; + const toY = (y) => PAD_T + PLOT_H - (y / yMax) * PLOT_H; + + const pointsFor = (values) => + values + .map((v, i) => (v === null ? null : `${toX(ns[i]).toFixed(1)},${toY(v).toFixed(1)}`)) + .filter(Boolean) + .join(" "); + + if (active.length === 0) return null; + return ( -
- {visible ? ( - - ) : ( -
- )} -
+
+ c.label).join(", ")} plotted against input size up to n = ${maxN}.`} + > + + + + {PAINT_ORDER.map((k) => series.find((s) => s.key === k)) + .filter(Boolean) + .map((s) => ( + + ))} + + + cost + + + 1 + + + {maxN} + + + input size (n) + + + +
+ {series.map((s) => ( + + + {s.label} + + ))} +
+
); }; diff --git a/app/components/ui/graphChart.jsx b/app/components/ui/graphChart.jsx deleted file mode 100755 index 1091e34..0000000 --- a/app/components/ui/graphChart.jsx +++ /dev/null @@ -1,222 +0,0 @@ -"use client"; -import React, { useEffect, useState } from "react"; -import { - LineChart, - Line, - XAxis, - YAxis, - Tooltip, - Legend, - ResponsiveContainer, - CartesianGrid, - ReferenceLine, -} from "recharts"; - -const ComplexityGraph = ({ - bestCase = null, - averageCase = null, - worstCase = null, - maxN = 100, - title = "Time Complexity Analysis" -}) => { - // Scale the chart down on phones instead of letting it overflow horizontally - const [isMobile, setIsMobile] = useState(false); - - useEffect(() => { - const mq = window.matchMedia("(max-width: 639px)"); - const update = () => setIsMobile(mq.matches); - update(); - mq.addEventListener("change", update); - return () => mq.removeEventListener("change", update); - }, []); - - const fontSize = isMobile ? 9 : 11; - const strokeWidth = isMobile ? 2 : 3; - const activeDotRadius = isMobile ? 4 : 6; - - // Generate data points with smoother curve for better visualization - const data = []; - for (let n = 1; n <= maxN; n++) { - data.push({ - n, - best: bestCase ? bestCase(n) : null, - average: averageCase ? averageCase(n) : null, - worst: worstCase ? worstCase(n) : null - }); - } - - // Custom tooltip component - const CustomTooltip = ({ active, payload, label }) => { - if (active && payload && payload.length) { - return ( -
-

n = {label}

- {payload.map((entry, index) => ( -
-
- - {entry.name}: {entry.value.toFixed(2)} - -
- ))} -
- ); - } - return null; - }; - - // Custom legend component - const renderLegend = (props) => { - const { payload } = props; - return ( -
- {payload.map((entry, index) => ( -
-
- {entry.value} -
- ))} -
- ); - }; - - return ( -
-

- {title} -

- - - - - - - - - - } - cursor={{ - stroke: '#9ca3af', - strokeWidth: 1, - strokeDasharray: "3 3" - }} - /> - - - - {bestCase && ( - - )} - - {averageCase && ( - - )} - - {worstCase && ( - - )} - - - - -
- ); -}; - -export default ComplexityGraph; \ No newline at end of file diff --git a/app/design-algorithm/fundamentals/amortized-analysis/content.jsx b/app/design-algorithm/fundamentals/amortized-analysis/content.jsx index e6bc121..8aa4875 100755 --- a/app/design-algorithm/fundamentals/amortized-analysis/content.jsx +++ b/app/design-algorithm/fundamentals/amortized-analysis/content.jsx @@ -2,6 +2,7 @@ import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed"; import { useTheme } from "@/app/contexts/ThemeContext"; import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed"; +import BackendEngineerCard from "@/app/components/ui/BackendEngineerCard"; import InContentAd from "@/app/components/ads/InContentAd"; // The real cost of the first 32 appends to a doubling array: mostly 1, with a @@ -104,8 +105,8 @@ const CostChart = () => {
- Most appends cost 1. The red spikes are resizes, and they double in height - — but they also halve in frequency, which is exactly why the average stays + Most appends cost 1. The red spikes are resizes, and they double in height, + but they also halve in frequency, which is exactly why the average stays flat.
@@ -264,7 +265,7 @@ const Content = () => { { points: "The guarantee is about the sequence, not any single operation.", subpoints: [ - "One individual append genuinely can cost Θ(n). Amortized analysis never claims otherwise — it claims those expensive operations cannot happen often.", + "One individual append genuinely can cost Θ(n). Amortized analysis never claims otherwise. It claims those expensive operations cannot happen often.", ], }, ]; @@ -291,7 +292,7 @@ const Content = () => { ]; const counterRows = [ - ["0", "0 0 0 0", "—", "—"], + ["0", "0 0 0 0", "-", "-"], ["1", "0 0 0 1", "1", "1"], ["2", "0 0 1 0", "2", "3"], ["3", "0 0 1 1", "1", "4"], @@ -307,7 +308,7 @@ const Content = () => { ["Binary counter increment", "Θ(log n) bit flips", "O(1)", "Bit i only flips every 2ⁱ increments"], ["Stack with multipop", "Θ(n) for one multipop", "O(1)", "Each element can only be popped once after being pushed"], ["Hash table insert with rehash", "Θ(n) on rehash", "O(1)", "Same doubling argument as the dynamic array"], - ["Union-Find (rank + path compression)", "Θ(log n)", "O(α(n))", "α is the inverse Ackermann function — effectively ≤ 4"], + ["Union-Find (rank + path compression)", "Θ(log n)", "O(α(n))", "α is the inverse Ackermann function: effectively ≤ 4"], ["Splay tree operations", "Θ(n)", "O(log n)", "A costly splay restructures the tree, making later ones cheap"], ["Fibonacci heap extract-min", "Θ(n)", "O(log n)", "Consolidation is deferred until an extract forces it"], ]; @@ -369,7 +370,7 @@ const Content = () => { const faqs = [ { q: "What is amortized analysis?", - a: "It is a way of measuring the cost of an operation by averaging it over a worst-case sequence of operations, rather than looking at a single operation in isolation. It is used when an occasional expensive operation is guaranteed to be paid for by many cheap ones — like appending to a dynamic array, where the rare resize is offset by all the appends that fit without resizing.", + a: "It is a way of measuring the cost of an operation by averaging it over a worst-case sequence of operations, rather than looking at a single operation in isolation. It is used when an occasional expensive operation is guaranteed to be paid for by many cheap ones: like appending to a dynamic array, where the rare resize is offset by all the appends that fit without resizing.", }, { q: "Is amortized analysis the same as average-case analysis?", @@ -389,15 +390,15 @@ const Content = () => { }, { q: "When is an amortized bound not good enough?", - a: "When any individual operation being slow is unacceptable. Real-time systems, safety-critical controllers and latency-sensitive services all care about the worst single operation, not the average across a sequence — a resize that stalls one request in a thousand still shows up as a p99 latency spike even though the amortized cost is O(1).", + a: "When any individual operation being slow is unacceptable. Real-time systems, safety-critical controllers and latency-sensitive services all care about the worst single operation, not the average across a sequence: a resize that stalls one request in a thousand still shows up as a p99 latency spike even though the amortized cost is O(1).", }, ]; return (
- - + +
@@ -406,7 +407,7 @@ const Content = () => { occasionally enormous. Appending to a dynamic array normally writes one slot, but when the buffer fills it allocates a bigger one and copies everything across. Quoting the worst case for that operation gives - O(n), which is technically correct and deeply misleading — it suggests + O(n), which is technically correct and deeply misleading. It suggests that building a list of a million items costs a trillion operations, when it actually costs about two million.

@@ -414,12 +415,12 @@ const Content = () => { Amortized analysis measures the average cost per operation across a worst-case sequence of operations. Instead of asking "how bad can one append be?", it asks "how bad can n appends be, in - total?" — and then divides. When the expensive cases are + total?" and then divides. When the expensive cases are structurally guaranteed to be rare, that average is the honest number.

-
+

A dynamic array keeps a buffer with some capacity. Appending writes one element; if the buffer is full, it first allocates one of double the @@ -444,14 +445,14 @@ const Content = () => { />

- The picture contains the entire argument. The spikes double in height — + The picture contains the entire argument. The spikes double in height, but they also double in spacing. Each resize is twice as expensive as the last and happens half as often, and those two effects cancel exactly.

-
+

The simplest method: add up the cost of the whole sequence, then divide by the number of operations. For n appends, the writes cost n, @@ -469,12 +470,12 @@ const Content = () => { Running this for real confirms it: the total cost of n appends divided by n settles at about 2.05 and stays there, whether n is a thousand or a million. The bound of 3 is comfortable, and the important point is - that it is a constant — it does not creep upward with n. + that it is a constant. It does not creep upward with n.

The reason doubling works is that the growth is geometric. If you instead grew the buffer by a fixed 10 slots each time, you would - resize n/10 times, copying an average of n/2 elements each time — + resize n/10 times, copying an average of n/2 elements each time: Θ(n²) in total, and Θ(n) amortized per append. The choice of growth factor is what creates the guarantee. @@ -504,16 +505,16 @@ const Content = () => { />

Measured over 100,000 increments, the ratio is 1.99998 flips per - increment — converging on 2 exactly as the series predicts. + increment: converging on 2 exactly as the series predicts.

-
+

Also called the banker's method. You invent a charge for - each operation — its amortized cost — which may be more or less than + each operation (its amortized cost), which may be more or less than what the operation really costs. The surplus is stored as{" "} credit on the data structure and later spent on expensive operations. @@ -527,11 +528,11 @@ const Content = () => {

For the dynamic array, charge 3 units per append and spend them as:

@@ -546,24 +547,24 @@ const Content = () => { operations can carry different charges. For a stack supporting push, pop and multipop(k), charge 2 for a push (1 to push, 1 banked for the eventual pop) and 0 for pop and multipop. Every pop is then paid for by - the credit its push left behind — so even a multipop that removes a + the credit its push left behind, so even a multipop that removes a thousand elements is free, because those thousand pushes already paid.

-
+

The physicist's method, and the most powerful of the three. Instead of tracking credit on individual elements, define a{" "} potential function Φ that maps the whole state of the data - structure to a number — the stored-up work it represents. + structure to a number: the stored-up work it represents.

amortized cost = actual cost + Φ(after) − Φ(before)

Summed over a sequence, the Φ terms telescope: every intermediate value appears once positive and once negative, leaving only Φ(end) − Φ(start). - So as long as Φ never drops below its starting value — usually - arranged by setting Φ(D₀) = 0 and keeping Φ ≥ 0 — the total amortized + So as long as Φ never drops below its starting value (usually + arranged by setting Φ(D₀) = 0 and keeping Φ ≥ 0), the total amortized cost is an upper bound on the total real cost.

@@ -586,7 +587,7 @@ const Content = () => {

Dynamic array. Let Φ = 2·size − capacity. Immediately after a resize, size is half of capacity so Φ = 0; as appends fill the buffer, - Φ climbs to equal capacity by the time the next resize is due — having + Φ climbs to equal capacity by the time the next resize is due: having accumulated precisely enough potential to fund the copy. Working through both cases gives an amortized cost of 3 per append, agreeing with the accounting method. @@ -600,7 +601,7 @@ const Content = () => { firstColMono={false} />

- All three are provably equivalent in power — any bound one can + All three are provably equivalent in power: any bound one can establish, the others can too. Aggregate analysis is the one to reach for when every operation is the same kind. The accounting method suits structures with several operation types. The potential method is the @@ -632,7 +633,7 @@ const Content = () => {

Union-Find is the most striking entry. With union by rank and path compression, a sequence of m operations costs O(m·α(n)), where α is the - inverse Ackermann function — a function that grows so slowly it is below + inverse Ackermann function: a function that grows so slowly it is below 5 for any n that could be written down in this universe. The per-operation worst case is still logarithmic; only the amortized analysis reveals that the structure is effectively constant-time. @@ -648,7 +649,7 @@ const Content = () => {

The engineering response is not to abandon the structure but to spread - the cost deliberately — incremental or background resizing, or + the cost deliberately: incremental or background resizing, or preallocating capacity up front when the final size is known. Both convert a rare large stall into a small predictable overhead on every operation. @@ -659,6 +660,14 @@ const Content = () => {

+ {/* Newsletter: inline, after the substantive sections and before the + end matter. NewsletterEmbed renders null below 768px (desktop-only + for mobile performance), so the wrapper hides at the same + breakpoint, otherwise this leaves an empty padded box on phones. */} +
+ +
+
{faqs.map((faq, index) => ( @@ -675,11 +684,11 @@ const Content = () => {
diff --git a/app/design-algorithm/fundamentals/amortized-analysis/page.jsx b/app/design-algorithm/fundamentals/amortized-analysis/page.jsx index 075eb0d..bd2721c 100755 --- a/app/design-algorithm/fundamentals/amortized-analysis/page.jsx +++ b/app/design-algorithm/fundamentals/amortized-analysis/page.jsx @@ -11,7 +11,7 @@ export const metadata = { alternates: { canonical: "/design-algorithm/fundamentals/amortized-analysis", }, - title: "Amortized Analysis — Aggregate, Accounting and Potential Methods", + title: "Amortized Analysis: Aggregate, Accounting and Potential Methods", description: "Learn amortized analysis: why an occasional expensive operation does not make a data structure slow. Covers the dynamic array doubling argument, the binary counter, all three methods (aggregate, accounting, potential), how amortized differs from average-case, and when an amortized bound is not good enough.", keywords: [ @@ -41,7 +41,7 @@ export const metadata = { }; // FAQ answers mirror the "Frequently Asked Questions" section in content.jsx. -// Keep the two in sync — structured data that does not match the visible page +// Keep the two in sync: structured data that does not match the visible page // is a manual-action risk. const faqJsonLd = { "@context": "https://schema.org", @@ -52,7 +52,7 @@ const faqJsonLd = { name: "What is amortized analysis?", acceptedAnswer: { "@type": "Answer", - text: "It is a way of measuring the cost of an operation by averaging it over a worst-case sequence of operations, rather than looking at a single operation in isolation. It is used when an occasional expensive operation is guaranteed to be paid for by many cheap ones — like appending to a dynamic array, where the rare resize is offset by all the appends that fit without resizing.", + text: "It is a way of measuring the cost of an operation by averaging it over a worst-case sequence of operations, rather than looking at a single operation in isolation. It is used when an occasional expensive operation is guaranteed to be paid for by many cheap ones: like appending to a dynamic array, where the rare resize is offset by all the appends that fit without resizing.", }, }, { @@ -92,7 +92,7 @@ const faqJsonLd = { name: "When is an amortized bound not good enough?", acceptedAnswer: { "@type": "Answer", - text: "When any individual operation being slow is unacceptable. Real-time systems, safety-critical controllers and latency-sensitive services all care about the worst single operation, not the average across a sequence — a resize that stalls one request in a thousand still shows up as a p99 latency spike even though the amortized cost is O(1).", + text: "When any individual operation being slow is unacceptable. Real-time systems, safety-critical controllers and latency-sensitive services all care about the worst single operation, not the average across a sequence: a resize that stalls one request in a thousand still shows up as a p99 latency spike even though the amortized cost is O(1).", }, }, ], diff --git a/app/design-algorithm/fundamentals/basics/asymptotic-notation/content.jsx b/app/design-algorithm/fundamentals/basics/asymptotic-notation/content.jsx index 0509e00..ef2fd40 100755 --- a/app/design-algorithm/fundamentals/basics/asymptotic-notation/content.jsx +++ b/app/design-algorithm/fundamentals/basics/asymptotic-notation/content.jsx @@ -2,6 +2,7 @@ import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed"; import { useTheme } from "@/app/contexts/ThemeContext"; import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed"; +import BackendEngineerCard from "@/app/components/ui/BackendEngineerCard"; import InContentAd from "@/app/components/ads/InContentAd"; // Plots f(n) against a scaled bound c*g(n) so the reader can see what "for all @@ -248,7 +249,7 @@ const Content = () => { { points: "It lets you reject a design before you build it.", subpoints: [ - "If a problem has a million inputs and your idea is quadratic, you know it is wrong on paper — no prototype required.", + "If a problem has a million inputs and your idea is quadratic, you know it is wrong on paper: no prototype required.", ], }, ]; @@ -256,11 +257,11 @@ const Content = () => { const simplify = [ { points: "Start with the exact operation count.", detail: "T(n) = 3n² + 5n + 7" }, { - points: "Drop the lower-order terms — they grow slower and become irrelevant.", + points: "Drop the lower-order terms. They grow slower and become irrelevant.", detail: "T(n) ≈ 3n²", }, { - points: "Drop the constant factor — it does not change the shape of the curve.", + points: "Drop the constant factor. It does not change the shape of the curve.", detail: "T(n) = Θ(n²)", }, ]; @@ -282,33 +283,33 @@ const Content = () => { const properties = [ { - points: "Transitivity — it holds for all five notations.", + points: "Transitivity. It holds for all five notations.", subpoints: ["If f = O(g) and g = O(h), then f = O(h)."], }, { - points: "Reflexivity — for O, Ω and Θ only.", + points: "Reflexivity: for O, Ω and Θ only.", subpoints: [ "f = O(f), f = Ω(f) and f = Θ(f) are always true. The strict forms are not reflexive: f is never o(f).", ], }, { - points: "Symmetry — Θ only.", + points: "Symmetry: Θ only.", subpoints: ["If f = Θ(g), then g = Θ(f). O and Ω are not symmetric."], }, { - points: "Transpose symmetry — O and Ω are mirrors.", + points: "Transpose symmetry: O and Ω are mirrors.", subpoints: [ "f = O(g) if and only if g = Ω(f). The same relationship links o and ω.", ], }, { - points: "The sum rule — the larger term absorbs the smaller.", + points: "The sum rule: the larger term absorbs the smaller.", subpoints: [ "O(f) + O(g) = O(max(f, g)). This is why sequential blocks of code collapse to whichever is slowest.", ], }, { - points: "The product rule — nested work multiplies.", + points: "The product rule: nested work multiplies.", subpoints: [ "O(f) × O(g) = O(f × g). This is why an O(log n) loop inside an O(n) loop is O(n log n).", ], @@ -359,7 +360,7 @@ const Content = () => { { points: "Different input sizes stay separate.", subpoints: [ - "A loop over n nested in a loop over m is O(n · m) — collapsing it to O(n²) is only correct if n and m are the same quantity.", + "A loop over n nested in a loop over m is O(n · m): collapsing it to O(n²) is only correct if n and m are the same quantity.", ], }, ]; @@ -400,7 +401,7 @@ const Content = () => { }, { points: "Writing O(2n) or O(n + 5).", - subpoints: ["Both are just O(n) — the point of the notation is to discard that detail."], + subpoints: ["Both are just O(n): the point of the notation is to discard that detail."], }, { points: "Using O where Θ is meant.", @@ -411,13 +412,13 @@ const Content = () => { { points: "Reading the equals sign as equality.", subpoints: [ - 'f(n) = O(g(n)) really means "f belongs to the set O(g)". That is why you can write n = O(n²) but never O(n²) = n — the relation only runs one way.', + 'f(n) = O(g(n)) really means "f belongs to the set O(g)". That is why you can write n = O(n²) but never O(n²) = n: the relation only runs one way.', ], }, { points: "Forgetting that constants matter for small n.", subpoints: [ - "An O(n log n) algorithm with a huge constant can lose to an O(n²) one on tiny inputs — which is why real sort implementations switch to insertion sort for short subarrays.", + "An O(n log n) algorithm with a huge constant can lose to an O(n²) one on tiny inputs, which is why real sort implementations switch to insertion sort for short subarrays.", ], }, { @@ -431,11 +432,11 @@ const Content = () => { const faqs = [ { q: "What is the difference between Big-O and Big-Θ?", - a: "Big-O is only a ceiling: it says the algorithm grows no faster than the given function, so an O(n) algorithm is technically also O(n²). Big-Θ is a two-sided claim — the function is both an upper and a lower bound — so it pins the growth rate exactly. Θ is the stronger statement, and you can only make it when the best and worst cases share the same growth.", + a: "Big-O is only a ceiling: it says the algorithm grows no faster than the given function, so an O(n) algorithm is technically also O(n²). Big-Θ is a two-sided claim (the function is both an upper and a lower bound), so it pins the growth rate exactly. Θ is the stronger statement, and you can only make it when the best and worst cases share the same growth.", }, { q: "Why do we ignore constants and lower-order terms?", - a: "Because they stop mattering as n grows. In 3n² + 5n + 7, the quadratic term accounts for 84% of the total at n = 10 and over 99.9% at n = 10,000. The constant 3 depends on your language and hardware anyway, so keeping it would make the answer machine-specific — exactly what asymptotic notation exists to avoid.", + a: "Because they stop mattering as n grows. In 3n² + 5n + 7, the quadratic term accounts for 84% of the total at n = 10 and over 99.9% at n = 10,000. The constant 3 depends on your language and hardware anyway, so keeping it would make the answer machine-specific: exactly what asymptotic notation exists to avoid.", }, { q: "Is Big-O the same thing as the worst case?", @@ -443,11 +444,11 @@ const Content = () => { }, { q: "Does the base of the logarithm matter in O(log n)?", - a: "No. Changing base multiplies by a constant — log₂n = log₁₀n / log₁₀2 — and constants are dropped, so log₂n, log₁₀n and ln n are all written O(log n). This is why binary search and a search that splits into ten parts have the same complexity even though one is measurably faster.", + a: "No. Changing base multiplies by a constant (log₂n = log₁₀n / log₁₀2), and constants are dropped, so log₂n, log₁₀n and ln n are all written O(log n). This is why binary search and a search that splits into ten parts have the same complexity even though one is measurably faster.", }, { q: "Can an algorithm be both O(n) and O(n²)?", - a: "Yes, and this is the most common source of confusion. Big-O is an upper bound, and n really does grow no faster than n², so the statement is true — just uselessly loose. By convention you always quote the tightest upper bound you can prove, which is why nobody writes O(n²) for a single loop.", + a: "Yes, and this is the most common source of confusion. Big-O is an upper bound, and n really does grow no faster than n², so the statement is true: just uselessly loose. By convention you always quote the tightest upper bound you can prove, which is why nobody writes O(n²) for a single loop.", }, { q: "How do I find the asymptotic notation of a piece of code?", @@ -458,16 +459,16 @@ const Content = () => { return (
- - + +

Asymptotic notation is a language for describing how the cost of an algorithm grows as its input gets larger. Instead of measuring an - algorithm in seconds — which depends on the machine, the compiler and - the mood of your operating system — we count the operations it + algorithm in seconds (which depends on the machine, the compiler and + the mood of your operating system), we count the operations it performs as a function of the input size n, and then keep only the part of that function that matters when n becomes large.

@@ -485,7 +486,7 @@ const Content = () => { Asymptotic notation is deliberately imprecise. Throwing away - constants is not a limitation of the technique — it is the entire + constants is not a limitation of the technique. It is the entire point, because it is what makes a complexity claim true on every machine rather than on yours. @@ -530,12 +531,12 @@ const Content = () => { At n = 10 the smaller terms still contribute a sixth of the total. By n = 10,000 they contribute less than a fiftieth of one percent. Since asymptotic analysis is about the behaviour as n keeps growing, keeping - those terms would add precision that is already noise — and would make + those terms would add precision that is already noise, and would make the answer depend on details no two machines agree on.

-
+

Big-O says an algorithm grows no faster than some function. It is the ceiling. @@ -556,7 +557,7 @@ const Content = () => { { label: "c·g(n)", fn: (n) => n * n, color: "#ef4444", dashed: true }, { label: "f(n)", fn: (n) => 0.5 * n * n + 3 * n + 5, color: "#3b82f6" }, ]} - caption="Past n₀ the red ceiling stays above f(n) — that is all Big-O requires." + caption="Past n₀ the red ceiling stays above f(n). That is all Big-O requires." />

Because it is only a ceiling, an O(n) algorithm is technically also @@ -593,7 +594,7 @@ const Content = () => { ]} />

- Note how crude the bound is — 15n² is five times larger than the + Note how crude the bound is: 15n² is five times larger than the function it bounds. That is fine. The definition never asks for the smallest possible c, only for some c that works, which is exactly why constant factors carry no information in the final answer. @@ -602,7 +603,7 @@ const Content = () => {

-
+

Big-Ω is the mirror image: the algorithm grows at least as fast as the given function. It is the floor. @@ -629,7 +630,7 @@ const Content = () => {

-
+

Θ is the strongest of the three: it holds when the same function is both an upper and a lower bound, so the growth rate is pinned exactly. @@ -645,12 +646,12 @@ const Content = () => { { label: "f(n)", fn: (n) => 0.5 * n * n + 3 * n + 5, color: "#3b82f6" }, { label: "c₁·g(n)", fn: (n) => 0.3 * n * n, color: "#10b981", dashed: true }, ]} - caption="f(n) is sandwiched between two scaled copies of the same g(n) — that is Θ." + caption="f(n) is sandwiched between two scaled copies of the same g(n). That is Θ." />

Equivalently: f(n) = Θ(g(n)) if and only if f(n) = O(g(n)) and f(n) = Ω(g(n)). Merge sort is Θ(n log n) because it never does better - and never does worse. Quick sort is not Θ(n log n) — its worst + and never does worse. Quick sort is not Θ(n log n). Its worst case is Θ(n²), so only a per-case statement is honest.

@@ -662,12 +663,12 @@ const Content = () => {

  • - f(n) = o(g(n)) — f grows strictly slower than g. The bound + f(n) = o(g(n)): f grows strictly slower than g. The bound must hold for every constant c, not just some c. Example: n = o(n²), but n is not o(n).
  • - f(n) = ω(g(n)) — f grows strictly faster than g. Example: + f(n) = ω(g(n)): f grows strictly faster than g. Example: n² = ω(n).
@@ -686,8 +687,8 @@ const Content = () => { />

The middle column is a useful mnemonic but not a perfect one. Unlike - numbers, two functions need not be comparable at all — you can - construct oscillating functions where neither is O of the other — so + numbers, two functions need not be comparable at all (you can + construct oscillating functions where neither is O of the other), so treat the ≤ / ≥ / = analogy as intuition rather than as a theorem.

@@ -702,8 +703,8 @@ const Content = () => {

- When two functions are hard to compare by eye — is n log n bigger than - n^1.5? — the limit test settles it mechanically. Evaluate the ratio as + When two functions are hard to compare by eye (is n log n bigger than + n^1.5?), the limit test settles it mechanically. Evaluate the ratio as n approaches infinity:

lim (n → ∞) f(n) / g(n) @@ -714,7 +715,7 @@ const Content = () => { />

For the example above, n log n divided by n^1.5 is log n / n^0.5, which - tends to 0, so n log n = o(n^1.5) — the linearithmic function is the + tends to 0, so n log n = o(n^1.5): the linearithmic function is the smaller of the two. This test is also the quickest way to confirm the standard ordering: 1 < log n < √n < n < n log n < n² < n³ < 2ⁿ < n!. @@ -803,11 +804,19 @@ const Content = () => { Notation describes the growth rate; best/average/worst describes{" "} which input you are talking about. Pick one from each column and - your statement will be unambiguous — for example, "quick sort is + your statement will be unambiguous: for example, "quick sort is Θ(n²) in the worst case and Θ(n log n) on average".

+ {/* Newsletter: inline, after the substantive sections and before the + end matter. NewsletterEmbed renders null below 768px (desktop-only + for mobile performance), so the wrapper hides at the same + breakpoint, otherwise this leaves an empty padded box on phones. */} +
+ +
+
{faqs.map((faq, index) => ( @@ -826,7 +835,7 @@ const Content = () => { items={[ { points: "Asymptotic notation describes growth as n → ∞, not speed at any particular size." }, { points: "O is an upper bound, Ω is a lower bound, Θ is both at once, and o and ω are their strict versions." }, - { points: "A bound only has to hold past some cutoff n₀, and you may scale g(n) by any constant — which is why constants never survive." }, + { points: "A bound only has to hold past some cutoff n₀, and you may scale g(n) by any constant, which is why constants never survive." }, { points: "Always quote the tightest bound you can justify; a loose O is true but says nothing." }, { points: "The equals sign in f = O(g) means set membership, so the relation only reads left to right." }, { points: "The limit test decides any comparison you cannot make by eye." }, diff --git a/app/design-algorithm/fundamentals/basics/asymptotic-notation/page.jsx b/app/design-algorithm/fundamentals/basics/asymptotic-notation/page.jsx index b0e90f4..33711e1 100755 --- a/app/design-algorithm/fundamentals/basics/asymptotic-notation/page.jsx +++ b/app/design-algorithm/fundamentals/basics/asymptotic-notation/page.jsx @@ -11,7 +11,7 @@ export const metadata = { alternates: { canonical: "/design-algorithm/fundamentals/basics/asymptotic-notation", }, - title: "Asymptotic Notation — Big-O, Big-Ω and Big-Θ Explained", + title: "Asymptotic Notation: Big-O, Big-Ω and Big-Θ Explained", description: "Understand asymptotic notation from the ground up: the formal definitions of Big-O, Big-Omega and Big-Theta, how little-o and little-omega differ, the common growth rates from O(1) to O(n!), the rules for simplifying an operation count, and the mistakes to avoid. Part of the Design & Analysis of Algorithms series.", keywords: [ @@ -42,7 +42,7 @@ export const metadata = { }; // FAQ answers mirror the "Frequently Asked Questions" section in content.jsx. -// Keep the two in sync — structured data that does not match the visible page +// Keep the two in sync: structured data that does not match the visible page // is a manual-action risk. const faqJsonLd = { "@context": "https://schema.org", @@ -53,7 +53,7 @@ const faqJsonLd = { name: "What is the difference between Big-O and Big-Theta?", acceptedAnswer: { "@type": "Answer", - text: "Big-O is only a ceiling: it says the algorithm grows no faster than the given function, so an O(n) algorithm is technically also O(n^2). Big-Theta is a two-sided claim — the function is both an upper and a lower bound — so it pins the growth rate exactly. Theta is the stronger statement, and you can only make it when the best and worst cases share the same growth.", + text: "Big-O is only a ceiling: it says the algorithm grows no faster than the given function, so an O(n) algorithm is technically also O(n^2). Big-Theta is a two-sided claim (the function is both an upper and a lower bound), so it pins the growth rate exactly. Theta is the stronger statement, and you can only make it when the best and worst cases share the same growth.", }, }, { @@ -61,7 +61,7 @@ const faqJsonLd = { name: "Why do we ignore constants and lower-order terms?", acceptedAnswer: { "@type": "Answer", - text: "Because they stop mattering as n grows. In 3n^2 + 5n + 7, the quadratic term accounts for 84% of the total at n = 10 and over 99.9% at n = 10,000. The constant 3 depends on your language and hardware anyway, so keeping it would make the answer machine-specific — exactly what asymptotic notation exists to avoid.", + text: "Because they stop mattering as n grows. In 3n^2 + 5n + 7, the quadratic term accounts for 84% of the total at n = 10 and over 99.9% at n = 10,000. The constant 3 depends on your language and hardware anyway, so keeping it would make the answer machine-specific: exactly what asymptotic notation exists to avoid.", }, }, { @@ -77,7 +77,7 @@ const faqJsonLd = { name: "Does the base of the logarithm matter in O(log n)?", acceptedAnswer: { "@type": "Answer", - text: "No. Changing base multiplies by a constant — log2(n) = log10(n) / log10(2) — and constants are dropped, so log2(n), log10(n) and ln(n) are all written O(log n). This is why binary search and a search that splits into ten parts have the same complexity even though one is measurably faster.", + text: "No. Changing base multiplies by a constant (log2(n) = log10(n) / log10(2)), and constants are dropped, so log2(n), log10(n) and ln(n) are all written O(log n). This is why binary search and a search that splits into ten parts have the same complexity even though one is measurably faster.", }, }, { @@ -85,7 +85,7 @@ const faqJsonLd = { name: "Can an algorithm be both O(n) and O(n^2)?", acceptedAnswer: { "@type": "Answer", - text: "Yes, and this is the most common source of confusion. Big-O is an upper bound, and n really does grow no faster than n^2, so the statement is true — just uselessly loose. By convention you always quote the tightest upper bound you can prove, which is why nobody writes O(n^2) for a single loop.", + text: "Yes, and this is the most common source of confusion. Big-O is an upper bound, and n really does grow no faster than n^2, so the statement is true: just uselessly loose. By convention you always quote the tightest upper bound you can prove, which is why nobody writes O(n^2) for a single loop.", }, }, { diff --git a/app/design-algorithm/fundamentals/basics/time-space-complexity/code.js b/app/design-algorithm/fundamentals/basics/time-space-complexity/code.js index 0067be8..0c22dc9 100755 --- a/app/design-algorithm/fundamentals/basics/time-space-complexity/code.js +++ b/app/design-algorithm/fundamentals/basics/time-space-complexity/code.js @@ -2,7 +2,7 @@ const codeExamples = { javascript: `// Comparing complexities of two solutions to the same problem // Problem: does the array contain any duplicate value? -// Approach 1 — compare every pair +// Approach 1: compare every pair // Time: O(n^2) ... two nested loops over n // Space: O(1) ... no extra structure is allocated function hasDuplicateBrute(arr) { @@ -14,7 +14,7 @@ function hasDuplicateBrute(arr) { return false; } -// Approach 2 — remember what we have already seen +// Approach 2. Remember what we have already seen // Time: O(n) ... one pass, O(1) average work per element // Space: O(n) ... the set can hold every element function hasDuplicateSet(arr) { diff --git a/app/design-algorithm/fundamentals/basics/time-space-complexity/content.jsx b/app/design-algorithm/fundamentals/basics/time-space-complexity/content.jsx index f045a6d..e5bf4f2 100755 --- a/app/design-algorithm/fundamentals/basics/time-space-complexity/content.jsx +++ b/app/design-algorithm/fundamentals/basics/time-space-complexity/content.jsx @@ -2,6 +2,7 @@ import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed"; import { useTheme } from "@/app/contexts/ThemeContext"; import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed"; +import BackendEngineerCard from "@/app/components/ui/BackendEngineerCard"; import InContentAd from "@/app/components/ads/InContentAd"; const Section = ({ title, children }) => ( @@ -132,7 +133,7 @@ const Content = () => { { points: "Assigning to a variable, reading a variable." }, { points: "Indexing an array: arr[i]." }, { points: "Following a reference or pointer: node.next." }, - { points: "Calling a function — the call itself, not the work inside it." }, + { points: "Calling a function: the call itself, not the work inside it." }, ]; const countingRows = [ @@ -152,7 +153,7 @@ const Content = () => { const loopRows = [ ["for (i = 0; i < n; i++)", "O(n)", "The counter increases by a constant, so it takes n steps to reach n."], - ["for (i = 0; i < n; i += 3)", "O(n)", "n/3 iterations — still linear, because constants are dropped."], + ["for (i = 0; i < n; i += 3)", "O(n)", "n/3 iterations: still linear, because constants are dropped."], [ "for (i = 1; i < n; i *= 2)", "O(log n)", @@ -171,7 +172,7 @@ const Content = () => { [ "for (i = 0; i < n; i++) for (j = 0; j < m; j++)", "O(n · m)", - "Two independent sizes must both appear — do not collapse this to O(n²).", + "Two independent sizes must both appear. Do not collapse this to O(n²).", ], [ "for (i = 0; i < n; i++) for (j = 1; j < n; j *= 2)", @@ -187,20 +188,20 @@ const Content = () => { const spaceCounts = [ { - points: "Auxiliary space — the extra memory your algorithm allocates.", + points: "Auxiliary space: the extra memory your algorithm allocates.", subpoints: [ "Temporary arrays, hash maps, the recursion call stack, and any buffers you create.", "This is what people almost always mean when they quote a space complexity.", ], }, { - points: "Input space — the memory the input itself occupies.", + points: "Input space: the memory the input itself occupies.", subpoints: [ "Total space complexity = input space + auxiliary space, but the input is usually excluded because you had to store it either way.", ], }, { - points: "The call stack — the part everyone forgets.", + points: "The call stack: the part everyone forgets.", subpoints: [ "Every pending recursive call holds its parameters and local variables in memory, so recursion depth is a real space cost.", ], @@ -211,7 +212,7 @@ const Content = () => { ["Iterative loop", "O(1)", "No frames are stacked; the loop reuses the same variables."], ["Linear recursion (factorial, linked-list traversal)", "O(n)", "n frames are open at the deepest point."], ["Binary search, recursive", "O(log n)", "The depth is the number of halvings."], - ["Merge sort", "O(n)", "O(log n) of stack plus O(n) for the merge buffer — the buffer dominates."], + ["Merge sort", "O(n)", "O(log n) of stack plus O(n) for the merge buffer: the buffer dominates."], ["Quick sort (with tail-call on the larger side)", "O(log n)", "Depth is O(log n) when partitions are balanced, O(n) in the worst case."], ["DFS on a graph", "O(V)", "A path can, in the worst case, contain every vertex."], ]; @@ -220,7 +221,7 @@ const Content = () => { { points: "Memoization trades memory for repeated work.", subpoints: [ - "Naive recursive Fibonacci is O(2ⁿ) time and O(n) space. Caching each result makes it O(n) time and O(n) space — an enormous win for a small, bounded cost.", + "Naive recursive Fibonacci is O(2ⁿ) time and O(n) space. Caching each result makes it O(n) time and O(n) space: an enormous win for a small, bounded cost.", ], }, { @@ -232,7 +233,7 @@ const Content = () => { { points: "Counting sort trades memory for a linear sort.", subpoints: [ - "It sorts in O(n + k) time by allocating a bucket for every possible value — fast when the value range k is small, wasteful when it is huge.", + "It sorts in O(n + k) time by allocating a bucket for every possible value: fast when the value range k is small, wasteful when it is huge.", ], }, { @@ -247,7 +248,7 @@ const Content = () => { ["Array (static)", "O(1)", "O(n)", "O(n)", "O(n)", "Access by index is the one thing arrays do instantly."], ["Dynamic array", "O(1)", "O(n)", "O(1)*", "O(n)", "*Amortized for appends; a resize copy is O(n)."], ["Singly linked list", "O(n)", "O(n)", "O(1)", "O(1)", "Insert/delete are O(1) only when you already hold the node."], - ["Hash table", "—", "O(1)", "O(1)", "O(1)", "Average case; degrades to O(n) with heavy collisions."], + ["Hash table", "-", "O(1)", "O(1)", "O(1)", "Average case; degrades to O(n) with heavy collisions."], ["Balanced BST", "O(log n)", "O(log n)", "O(log n)", "O(log n)", "Keeps data sorted, unlike a hash table."], ["Binary heap", "O(1) (min/max)", "O(n)", "O(log n)", "O(log n)", "Only the extreme element is cheap to reach."], ]; @@ -276,7 +277,7 @@ const Content = () => { { points: "Decide what n actually is.", subpoints: [ - "The number of array elements, the number of nodes and edges, the length of the string, the number of digits — name it before you count anything.", + "The number of array elements, the number of nodes and edges, the length of the string, the number of digits: name it before you count anything.", ], }, { @@ -323,7 +324,7 @@ const Content = () => { { points: "Forgetting the recursion stack in space analysis.", subpoints: [ - 'A recursive function that allocates nothing is still not O(1) space — a depth-n recursion holds n frames, which is exactly why deep recursion throws a "maximum call stack size exceeded" error.', + 'A recursive function that allocates nothing is still not O(1) space: a depth-n recursion holds n frames, which is exactly why deep recursion throws a "maximum call stack size exceeded" error.', ], }, { @@ -353,7 +354,7 @@ const Content = () => { }, { q: "Which matters more, time or space?", - a: "On modern hardware, time usually matters more, because memory is comparatively cheap and plentiful. The exception is any environment with a hard memory ceiling — embedded devices, large datasets that must stay in RAM, or a competitive-programming problem with a 256 MB limit — where an O(n) algorithm that allocates O(n²) memory simply cannot run.", + a: "On modern hardware, time usually matters more, because memory is comparatively cheap and plentiful. The exception is any environment with a hard memory ceiling (embedded devices, large datasets that must stay in RAM, or a competitive-programming problem with a 256 MB limit), where an O(n) algorithm that allocates O(n²) memory simply cannot run.", }, { q: "Does an O(1) algorithm always beat an O(n) one?", @@ -361,7 +362,7 @@ const Content = () => { }, { q: "How do I find the space complexity of a recursive function?", - a: "Take the maximum depth of the recursion tree and multiply it by the space each frame uses, then add any data structures allocated outside the recursion. Depth, not the total number of calls, is what counts — only the frames on the current path are alive at once.", + a: "Take the maximum depth of the recursion tree and multiply it by the space each frame uses, then add any data structures allocated outside the recursion. Depth, not the total number of calls, is what counts: only the frames on the current path are alive at once.", }, { q: "What does amortized complexity mean here?", @@ -372,8 +373,8 @@ const Content = () => { return (
- - + +
@@ -388,7 +389,7 @@ const Content = () => {

The word "grows" is the important one. Neither measure is - interested in a single number — not milliseconds, not kilobytes. Both + interested in a single number, not milliseconds, not kilobytes. Both answer the same shaped question: if the input doubles, what happens? An O(n) algorithm does twice the work. An O(n²) algorithm does four times the work. An O(log n) algorithm does one @@ -400,7 +401,7 @@ const Content = () => {

- Timing code with a stopwatch is genuinely useful — it is called + Timing code with a stopwatch is genuinely useful. It is called benchmarking, and you should do it before optimising anything. But it cannot replace complexity analysis, for four reasons:

@@ -420,7 +421,7 @@ const Content = () => {

- The model is a deliberate simplification — it ignores CPU caches, + The model is a deliberate simplification. It ignores CPU caches, branch prediction and memory latency, all of which matter in real benchmarks. What it buys you is a count that is independent of any particular processor, which is exactly what makes complexity @@ -447,7 +448,7 @@ const Content = () => {

Adding the column gives T(n) = 3n + 4. Now apply the two simplification rules: drop the lower-order term (the constant 4) and - drop the constant factor (the 3). What remains is O(n) — the + drop the constant factor (the 3). What remains is O(n): the cost is proportional to the number of elements, which matches the intuition that you must look at every element to add it up.

@@ -463,7 +464,7 @@ const Content = () => {

Two inputs of the same size can cost wildly different amounts, so a single number is often not enough. Linear search is the classic - illustration — it stops as soon as it finds the target: + illustration. It stops as soon as it finds the target:

{`function linearSearch(arr, target) { for (let i = 0; i < arr.length; i++) { @@ -478,7 +479,7 @@ const Content = () => { />

The average case assumes every position is equally likely, which - gives n/2 comparisons — still O(n) after dropping the constant. In + gives n/2 comparisons: still O(n) after dropping the constant. In practice the worst case is quoted most often, because it is the only one that comes with a guarantee. The average case matters when an algorithm's worst case is rare and pathological, which is @@ -518,8 +519,8 @@ const Content = () => {

A recursive function does not wear its cost on its sleeve, because the work is spread across a tree of calls. The standard technique is to - write a recurrence relation — an equation that defines the cost - of size n in terms of smaller sizes — and then solve it. + write a recurrence relation (an equation that defines the cost + of size n in terms of smaller sizes), and then solve it.

{`function mergeSort(arr) { if (arr.length <= 1) return arr; // O(1) @@ -531,7 +532,7 @@ const Content = () => {

Reading that directly off the code gives T(n) = 2T(n/2) + O(n): two subproblems of half the size, plus a linear merge. Solving it yields - O(n log n) — there are log₂n levels of recursion, and every level does + O(n log n). There are log₂n levels of recursion, and every level does a total of O(n) work merging.

@@ -543,8 +544,8 @@ const Content = () => {

- Space complexity is measured the same way as time — as growth, not as - a byte count — but it is split into parts that are easy to confuse: + Space complexity is measured the same way as time (as growth, not as + a byte count), but it is split into parts that are easy to confuse:

@@ -628,7 +629,7 @@ const Content = () => { Quick sort is usually the fastest in practice despite its O(n²) worst case, because its constant factors are small and it needs no merge buffer. Counting sort escapes the O(n log n) lower bound entirely by - not comparing elements at all — which it can only do because it + not comparing elements at all, which it can only do because it assumes the keys are small integers.

@@ -648,7 +649,7 @@ const Content = () => {

Used in reverse, the table is a strong hint. If a problem states that n can be up to 200,000, an O(n²) solution would need roughly 4 × 10¹⁰ - operations and is hopeless — so the intended answer is almost + operations and is hopeless, so the intended answer is almost certainly O(n log n) or better, and you can stop trying to make nested loops work.

@@ -662,6 +663,14 @@ const Content = () => {
+ {/* Newsletter: inline, after the substantive sections and before the + end matter. NewsletterEmbed renders null below 768px (desktop-only + for mobile performance), so the wrapper hides at the same + breakpoint, otherwise this leaves an empty padded box on phones. */} +
+ +
+
{faqs.map((faq, index) => ( @@ -684,7 +693,7 @@ const Content = () => { { points: "Time complexity counts operations; space complexity counts the memory alive at the peak, including the recursion stack." }, { points: "Nested loops multiply, sequential loops add, and only the dominant term survives." }, { points: "Best, average and worst case describe the input; O, Ω and Θ describe the bound. They are independent." }, - { points: "Memory can usually be traded for speed — memoization, hashing and precomputed tables are all the same bargain." }, + { points: "Memory can usually be traded for speed: memoization, hashing and precomputed tables are all the same bargain." }, { points: "Let the input size pick your target complexity before you start writing code." }, ]} /> diff --git a/app/design-algorithm/fundamentals/basics/time-space-complexity/page.jsx b/app/design-algorithm/fundamentals/basics/time-space-complexity/page.jsx index ac0d0a0..35285d7 100755 --- a/app/design-algorithm/fundamentals/basics/time-space-complexity/page.jsx +++ b/app/design-algorithm/fundamentals/basics/time-space-complexity/page.jsx @@ -11,7 +11,7 @@ export const metadata = { alternates: { canonical: "/design-algorithm/fundamentals/basics/time-space-complexity", }, - title: "Time and Space Complexity — A Complete Guide with Examples", + title: "Time and Space Complexity: A Complete Guide with Examples", description: "A complete guide to time and space complexity: how to count operations, analyse loops and recursion, the hidden cost of the call stack, best/average/worst case, the time-space trade-off, complexity tables for common data structures and sorting algorithms, and how to pick a target complexity from the input size.", keywords: [ @@ -42,7 +42,7 @@ export const metadata = { }; // FAQ answers mirror the "Frequently Asked Questions" section in content.jsx. -// Keep the two in sync — structured data that does not match the visible page +// Keep the two in sync: structured data that does not match the visible page // is a manual-action risk. const faqJsonLd = { "@context": "https://schema.org", @@ -61,7 +61,7 @@ const faqJsonLd = { name: "Which matters more, time or space?", acceptedAnswer: { "@type": "Answer", - text: "On modern hardware, time usually matters more, because memory is comparatively cheap and plentiful. The exception is any environment with a hard memory ceiling — embedded devices, large datasets that must stay in RAM, or a competitive-programming problem with a 256 MB limit — where an O(n) algorithm that allocates O(n^2) memory simply cannot run.", + text: "On modern hardware, time usually matters more, because memory is comparatively cheap and plentiful. The exception is any environment with a hard memory ceiling (embedded devices, large datasets that must stay in RAM, or a competitive-programming problem with a 256 MB limit), where an O(n) algorithm that allocates O(n^2) memory simply cannot run.", }, }, { @@ -77,7 +77,7 @@ const faqJsonLd = { name: "How do I find the space complexity of a recursive function?", acceptedAnswer: { "@type": "Answer", - text: "Take the maximum depth of the recursion tree and multiply it by the space each frame uses, then add any data structures allocated outside the recursion. Depth, not the total number of calls, is what counts — only the frames on the current path are alive at once.", + text: "Take the maximum depth of the recursion tree and multiply it by the space each frame uses, then add any data structures allocated outside the recursion. Depth, not the total number of calls, is what counts: only the frames on the current path are alive at once.", }, }, { @@ -121,7 +121,7 @@ export default function Page() {
diff --git a/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/code.js b/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/code.js index a33163a..9af70bb 100755 --- a/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/code.js +++ b/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/code.js @@ -67,7 +67,7 @@ function masterTheorem(a, b, fExp) { if (Math.abs(fExp - watershed) <= EPS) { return \`Case 2: T(n) = Theta(n^\${watershed.toFixed(3)} log n)\`; } - return \`Case 3: T(n) = Theta(n^\${fExp}) — verify a*f(n/b) <= c*f(n)\`; + return \`Case 3: T(n) = Theta(n^\${fExp}): verify a*f(n/b) <= c*f(n)\`; } console.log(masterTheorem(2, 2, 1)); // Case 2: Theta(n log n) diff --git a/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/content.jsx b/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/content.jsx index 668a330..1bb6060 100755 --- a/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/content.jsx +++ b/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/content.jsx @@ -2,6 +2,7 @@ import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed"; import { useTheme } from "@/app/contexts/ThemeContext"; import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed"; +import BackendEngineerCard from "@/app/components/ui/BackendEngineerCard"; import InContentAd from "@/app/components/ads/InContentAd"; // A three-level recursion tree for T(n) = a*T(n/b) + f(n). Seeing that every @@ -196,19 +197,19 @@ const Content = () => { const parts = [ { - points: "a — how many subproblems each call creates.", + points: "a: how many subproblems each call creates.", subpoints: [ "Merge sort makes two recursive calls, so a = 2. It must be a constant ≥ 1; it does not have to equal b.", ], }, { - points: "b — the factor by which the input shrinks.", + points: "b: the factor by which the input shrinks.", subpoints: [ "Halving the array means b = 2. It must be a constant greater than 1, otherwise the recursion never reaches the base case.", ], }, { - points: "f(n) — everything the call does outside the recursion.", + points: "f(n): everything the call does outside the recursion.", subpoints: [ "The splitting, the merging, the partitioning, the combining. For merge sort this is the O(n) merge.", ], @@ -247,12 +248,12 @@ const Content = () => { const exampleRows = [ ["T(n) = 2T(n/2) + n", "2", "2", "n¹", "Case 2", "Θ(n log n)", "Merge sort"], ["T(n) = T(n/2) + 1", "1", "2", "n⁰ = 1", "Case 2", "Θ(log n)", "Binary search"], - ["T(n) = 4T(n/2) + n", "4", "2", "n²", "Case 1", "Θ(n²)", "—"], - ["T(n) = 9T(n/3) + n", "9", "3", "n²", "Case 1", "Θ(n²)", "—"], + ["T(n) = 4T(n/2) + n", "4", "2", "n²", "Case 1", "Θ(n²)", "-"], + ["T(n) = 9T(n/3) + n", "9", "3", "n²", "Case 1", "Θ(n²)", "-"], ["T(n) = 7T(n/2) + n²", "7", "2", "n^2.807", "Case 1", "Θ(n^log₂7)", "Strassen"], - ["T(n) = 2T(n/2) + n²", "2", "2", "n¹", "Case 3", "Θ(n²)", "—"], - ["T(n) = 3T(n/4) + n log n", "3", "4", "n^0.793", "Case 3", "Θ(n log n)", "—"], - ["T(n) = 8T(n/2) + n³", "8", "2", "n³", "Case 2", "Θ(n³ log n)", "—"], + ["T(n) = 2T(n/2) + n²", "2", "2", "n¹", "Case 3", "Θ(n²)", "-"], + ["T(n) = 3T(n/4) + n log n", "3", "4", "n^0.793", "Case 3", "Θ(n log n)", "-"], + ["T(n) = 8T(n/2) + n³", "8", "2", "n³", "Case 2", "Θ(n³ log n)", "-"], ]; const failRows = [ @@ -327,7 +328,7 @@ const Content = () => { { points: "Skipping the regularity condition in Case 3.", subpoints: [ - "It holds for every polynomial f, which is why it is easy to forget — but it is part of the case, and there are standard exercises built on functions where it fails.", + "It holds for every polynomial f, which is why it is easy to forget, but it is part of the case, and there are standard exercises built on functions where it fails.", ], }, { @@ -363,7 +364,7 @@ const Content = () => { }, { q: "What do a, b and f(n) mean?", - a: "a is the number of subproblems each call creates, b is the factor by which the input size shrinks in each subproblem, and f(n) is the work done outside the recursive calls — the dividing and combining. For merge sort, a = 2, b = 2 and f(n) = n, because it makes two half-size calls and merges in linear time.", + a: "a is the number of subproblems each call creates, b is the factor by which the input size shrinks in each subproblem, and f(n) is the work done outside the recursive calls: the dividing and combining. For merge sort, a = 2, b = 2 and f(n) = n, because it makes two half-size calls and merges in linear time.", }, { q: "Why does merge sort come out as Θ(n log n)?", @@ -379,15 +380,15 @@ const Content = () => { }, { q: "What is the difference between the Master Theorem and the recursion tree method?", - a: "The Master Theorem is a shortcut — three cases, no work, but it only fits recurrences of one exact shape. The recursion tree method is a general technique that works on any recurrence, including unequal splits and subtractive ones, at the cost of doing the summation yourself. The Master Theorem is really just the recursion tree argument, solved once in general.", + a: "The Master Theorem is a shortcut: three cases, no work, but it only fits recurrences of one exact shape. The recursion tree method is a general technique that works on any recurrence, including unequal splits and subtractive ones, at the cost of doing the summation yourself. The Master Theorem is really just the recursion tree argument, solved once in general.", }, ]; return (
- - + +
@@ -396,14 +397,14 @@ const Content = () => { itself. Merge sort sorts an array by sorting two half-arrays and merging the results, so its running time obeys T(n) = 2T(n/2) + n. That equation is called a recurrence relation, and it is not an - answer — you cannot look at it and say how fast merge sort is. + answer. You cannot look at it and say how fast merge sort is.

Solving the recurrence means turning it into a closed form like Θ(n log n). There are three standard ways to do that: expand it into a recursion tree and sum the levels, guess an answer and prove it by - induction (the substitution method), or — when the recurrence has the - right shape — apply the Master Theorem and simply read the + induction (the substitution method), or (when the recurrence has the + right shape), apply the Master Theorem and simply read the answer off. This page is about the third.

@@ -421,8 +422,8 @@ const Content = () => { T(n) = a·T(n/b) + f(n), where a ≥ 1 and b > 1 are constants

- If your recurrence does not fit this template — different-sized - subproblems, a subtractive step, a non-constant a — the Master Theorem + If your recurrence does not fit this template (different-sized + subproblems, a subtractive step, a non-constant a), the Master Theorem does not apply, and no amount of rearranging will change that. The section on limitations below covers what to do instead.

@@ -493,7 +494,7 @@ const Content = () => {

The ε in Cases 1 and 3 matters more than it looks. It requires f to be{" "} - polynomially smaller or larger — smaller or larger by a factor + polynomially smaller or larger: smaller or larger by a factor of n^ε for some ε > 0. Being smaller by a factor of log n does not qualify, and that is precisely the gap where the theorem gives no answer at all. @@ -506,7 +507,7 @@ const Content = () => {

-
+

Solve T(n) = 2T(n/2) + n.

{

-
+

Solve T(n) = 4T(n/2) + n.

{ { points: "Identify the parts.", subpoints: ["a = 4, b = 2, f(n) = n."] }, { points: "Compute the watershed function.", - subpoints: ["n^(log₂4) = n² — four subproblems of half the size produce n² leaves."], + subpoints: ["n^(log₂4) = n²: four subproblems of half the size produce n² leaves."], }, { points: "Compare.", @@ -552,12 +553,12 @@ const Content = () => { ]} />

- The linear work at the root is irrelevant here — the tree branches so + The linear work at the root is irrelevant here: the tree branches so fast that almost all the cost sits in the leaves.

-
+

Solve T(n) = 2T(n/2) + n².

{

Strassen's matrix multiplication is the most interesting row. It replaces eight recursive multiplications with seven, dropping a from 8 - to 7 — and since the answer is Θ(n^(log₂a)), that single change takes + to 7, and since the answer is Θ(n^(log₂a)), that single change takes the running time from Θ(n³) to roughly Θ(n^2.807). It is a direct, practical demonstration that a is the parameter worth fighting over.

@@ -615,7 +616,7 @@ const Content = () => {

The first row is the classic gap case. Here f(n) = n/log n is smaller than the watershed n, but only by a logarithmic factor, not a - polynomial one — so Case 1 does not apply, and it is not equal to n + polynomial one, so Case 1 does not apply, and it is not equal to n either, so Case 2 does not apply. The theorem is simply silent, and you need a recursion tree to find the real answer.

@@ -625,6 +626,14 @@ const Content = () => {
+ {/* Newsletter: inline, after the substantive sections and before the + end matter. NewsletterEmbed renders null below 768px (desktop-only + for mobile performance), so the wrapper hides at the same + breakpoint, otherwise this leaves an empty padded box on phones. */} +
+ +
+
{faqs.map((faq, index) => ( @@ -646,7 +655,7 @@ const Content = () => { { points: "Case 1: leaves dominate → Θ(n^(log_b a)). Case 2: levels tie → Θ(n^(log_b a) log n). Case 3: root dominates → Θ(f(n))." }, { points: "The difference in Cases 1 and 3 must be polynomial; a log factor is not enough, and that gap is where the theorem stays silent." }, { points: "Case 3 is not finished until you have checked the regularity condition a·f(n/b) ≤ c·f(n)." }, - { points: "Unequal splits, subtractive recurrences and non-constant a all fall outside the theorem — reach for a recursion tree instead." }, + { points: "Unequal splits, subtractive recurrences and non-constant a all fall outside the theorem: reach for a recursion tree instead." }, ]} />
diff --git a/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/page.jsx b/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/page.jsx index 065367d..bde87b8 100755 --- a/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/page.jsx +++ b/app/design-algorithm/fundamentals/recurrence-relations/master-theorem/page.jsx @@ -11,7 +11,7 @@ export const metadata = { alternates: { canonical: "/design-algorithm/fundamentals/recurrence-relations/master-theorem", }, - title: "Master Theorem — Solve Divide and Conquer Recurrences", + title: "Master Theorem: Solve Divide and Conquer Recurrences", description: "Learn the Master Theorem for solving divide-and-conquer recurrences of the form T(n) = aT(n/b) + f(n). Covers where the three cases come from, the watershed function, the regularity condition, worked examples for merge sort, binary search and Strassen's algorithm, and the recurrences the theorem cannot solve.", keywords: [ @@ -41,7 +41,7 @@ export const metadata = { }; // FAQ answers mirror the "Frequently Asked Questions" section in content.jsx. -// Keep the two in sync — structured data that does not match the visible page +// Keep the two in sync: structured data that does not match the visible page // is a manual-action risk. const faqJsonLd = { "@context": "https://schema.org", @@ -60,7 +60,7 @@ const faqJsonLd = { name: "What do a, b and f(n) mean in the Master Theorem?", acceptedAnswer: { "@type": "Answer", - text: "a is the number of subproblems each call creates, b is the factor by which the input size shrinks in each subproblem, and f(n) is the work done outside the recursive calls — the dividing and combining. For merge sort, a = 2, b = 2 and f(n) = n, because it makes two half-size calls and merges in linear time.", + text: "a is the number of subproblems each call creates, b is the factor by which the input size shrinks in each subproblem, and f(n) is the work done outside the recursive calls: the dividing and combining. For merge sort, a = 2, b = 2 and f(n) = n, because it makes two half-size calls and merges in linear time.", }, }, { @@ -92,7 +92,7 @@ const faqJsonLd = { name: "What is the difference between the Master Theorem and the recursion tree method?", acceptedAnswer: { "@type": "Answer", - text: "The Master Theorem is a shortcut — three cases, no work, but it only fits recurrences of one exact shape. The recursion tree method is a general technique that works on any recurrence, including unequal splits and subtractive ones, at the cost of doing the summation yourself. The Master Theorem is really just the recursion tree argument, solved once in general.", + text: "The Master Theorem is a shortcut: three cases, no work, but it only fits recurrences of one exact shape. The recursion tree method is a general technique that works on any recurrence, including unequal splits and subtractive ones, at the cost of doing the summation yourself. The Master Theorem is really just the recursion tree argument, solved once in general.", }, }, ], diff --git a/app/design-algorithm/fundamentals/recurrence-relations/recursion-tree/content.jsx b/app/design-algorithm/fundamentals/recurrence-relations/recursion-tree/content.jsx index b302679..a607472 100755 --- a/app/design-algorithm/fundamentals/recurrence-relations/recursion-tree/content.jsx +++ b/app/design-algorithm/fundamentals/recurrence-relations/recursion-tree/content.jsx @@ -2,6 +2,7 @@ import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed"; import { useTheme } from "@/app/contexts/ThemeContext"; import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed"; +import BackendEngineerCard from "@/app/components/ui/BackendEngineerCard"; import InContentAd from "@/app/components/ads/InContentAd"; // A recursion tree drawn from a level description. Each node carries the size @@ -266,7 +267,7 @@ const Content = () => { { points: "A node holds the non-recursive cost of one call.", subpoints: [ - "Not the total cost of that call — only the f(n) part. The recursive work is represented by the node's children.", + "Not the total cost of that call: only the f(n) part. The recursive work is represented by the node's children.", ], }, { @@ -284,7 +285,7 @@ const Content = () => { { points: "The depth is how many times you can shrink n before hitting the base case.", subpoints: [ - "Dividing by b gives depth log_b n. Subtracting a constant gives depth n — a much taller tree.", + "Dividing by b gives depth log_b n. Subtracting a constant gives depth n: a much taller tree.", ], }, { @@ -339,7 +340,7 @@ const Content = () => { "Recursion tree", "Any recurrence", "Draw and sum", - "The answer plus the intuition — but informally", + "The answer plus the intuition, but informally", ], [ "Master Theorem", @@ -373,7 +374,7 @@ const Content = () => { { points: "Find the depth of the tree.", subpoints: [ - "Solve for when the subproblem size reaches 1 — log_b n for division, n for subtraction.", + "Solve for when the subproblem size reaches 1: log_b n for division, n for subtraction.", ], }, { @@ -418,7 +419,7 @@ const Content = () => { { points: "Summing a geometric series as though it were constant.", subpoints: [ - "If levels shrink by a constant ratio, the total is a constant multiple of the first level — do not multiply by the depth.", + "If levels shrink by a constant ratio, the total is a constant multiple of the first level. Do not multiply by the depth.", ], }, { @@ -448,7 +449,7 @@ const Content = () => { }, { q: "When is a recursion tree better than the Master Theorem?", - a: "Whenever the Master Theorem does not apply — unequal subproblem sizes such as T(n/3) + T(2n/3), subtractive recurrences such as T(n − 1), or recurrences that fall into the theorem's gaps like T(n) = 2T(n/2) + n/log n. The tree handles all of these, because it makes no assumption about the shape of the recurrence.", + a: "Whenever the Master Theorem does not apply: unequal subproblem sizes such as T(n/3) + T(2n/3), subtractive recurrences such as T(n − 1), or recurrences that fall into the theorem's gaps like T(n) = 2T(n/2) + n/log n. The tree handles all of these, because it makes no assumption about the shape of the recurrence.", }, { q: "What do the three tree shapes mean?", @@ -459,8 +460,8 @@ const Content = () => { return (
- - + +
@@ -476,7 +477,7 @@ const Content = () => { an answer and explains it. The Master Theorem gives you a result with no insight into where it came from. The substitution method proves a result you must already have guessed. The recursion tree shows you - where the time actually goes — and it works on recurrences the other + where the time actually goes, and it works on recurrences the other two cannot express.

@@ -491,7 +492,7 @@ const Content = () => {
-
+

The merge sort recurrence. Each call does n units of work merging, then hands two half-size problems to its children: @@ -527,7 +528,7 @@ const Content = () => {

-
+

Solve T(n) = 3T(n/4) + n². Now the node count triples while each subproblem's cost drops by a factor of 16, so the levels do{" "} @@ -540,7 +541,7 @@ const Content = () => {

Each level costs 3/16 of the one above it. That is a decreasing geometric series, and a geometric series is dominated by its first - term — so instead of multiplying by the depth, sum the series: + term, so instead of multiplying by the depth, sum the series:

{ The leaves are negligible here: there are 3^(log₄n) = n^(log₄3) ≈ n^0.79 of them, far fewer than the n² work done at the root. Computing the ratio T(n)/n² numerically converges to 1.2308, which is 16/13 - exactly — the constant the series predicts. + exactly: the constant the series predicts.

@@ -577,7 +578,7 @@ const Content = () => {

-
+

Solve T(n) = T(n/3) + T(2n/3) + n. There is no single b here, so the Master Theorem cannot even state this recurrence. The tree handles it @@ -602,15 +603,15 @@ const Content = () => { }, { points: "The longest path multiplies by 2/3 each step.", - subpoints: ["It survives for log_{3/2}n levels — the height of the tree."], + subpoints: ["It survives for log_{3/2}n levels: the height of the tree."], }, ]} />

Above the shortest leaf, every level costs exactly n; below it, levels cost at most n. That brackets the total between n·log₃n and - n·log_{"{3/2}"}n. Both are Θ(n log n) — the bases differ only by a - constant factor — so T(n) = Θ(n log n). + n·log_{"{3/2}"}n. Both are Θ(n log n) (the bases differ only by a + constant factor), so T(n) = Θ(n log n).

This is the practical case that matters most: it is the recurrence for @@ -620,7 +621,7 @@ const Content = () => {
-
+

Solve T(n) = 2T(n/2) + n/log n. The Master Theorem is silent here: the watershed is n, and n/log n is smaller than n but not{" "} @@ -643,7 +644,7 @@ const Content = () => { />

The harmonic series is what the Master Theorem's gap is hiding. - Neither geometric nor constant, it sums to a logarithm — giving an + Neither geometric nor constant, it sums to a logarithm: giving an answer, n log log n, that none of the three cases could have produced. Evaluating the recurrence numerically confirms it: T(n)/(n log log n) settles at about 0.72 and stays there as n grows. @@ -675,6 +676,14 @@ const Content = () => {

+ {/* Newsletter: inline, after the substantive sections and before the + end matter. NewsletterEmbed renders null below 768px (desktop-only + for mobile performance), so the wrapper hides at the same + breakpoint, otherwise this leaves an empty padded box on phones. */} +
+ +
+
{faqs.map((faq, index) => ( @@ -692,10 +701,10 @@ const Content = () => { diff --git a/app/design-algorithm/fundamentals/recurrence-relations/recursion-tree/page.jsx b/app/design-algorithm/fundamentals/recurrence-relations/recursion-tree/page.jsx index ef6a982..aa6b22e 100755 --- a/app/design-algorithm/fundamentals/recurrence-relations/recursion-tree/page.jsx +++ b/app/design-algorithm/fundamentals/recurrence-relations/recursion-tree/page.jsx @@ -11,7 +11,7 @@ export const metadata = { alternates: { canonical: "/design-algorithm/fundamentals/recurrence-relations/recursion-tree", }, - title: "Recursion Tree Method — Solve Recurrences by Summing Levels", + title: "Recursion Tree Method: Solve Recurrences by Summing Levels", description: "Learn the recursion tree method for solving recurrence relations: draw the calls as a tree, cost each level, and sum. Covers the anatomy of a tree, worked examples for T(n) = 2T(n/2) + n, 3T(n/4) + n², the unequal split T(n/3) + T(2n/3) + n, and the Master Theorem gap case 2T(n/2) + n/log n.", keywords: [ @@ -40,7 +40,7 @@ export const metadata = { }; // FAQ answers mirror the "Frequently Asked Questions" section in content.jsx. -// Keep the two in sync — structured data that does not match the visible page +// Keep the two in sync: structured data that does not match the visible page // is a manual-action risk. const faqJsonLd = { "@context": "https://schema.org", @@ -83,7 +83,7 @@ const faqJsonLd = { name: "When is a recursion tree better than the Master Theorem?", acceptedAnswer: { "@type": "Answer", - text: "Whenever the Master Theorem does not apply — unequal subproblem sizes such as T(n/3) + T(2n/3), subtractive recurrences such as T(n - 1), or recurrences that fall into the theorem's gaps like T(n) = 2T(n/2) + n/log n. The tree handles all of these, because it makes no assumption about the shape of the recurrence.", + text: "Whenever the Master Theorem does not apply: unequal subproblem sizes such as T(n/3) + T(2n/3), subtractive recurrences such as T(n - 1), or recurrences that fall into the theorem's gaps like T(n) = 2T(n/2) + n/log n. The tree handles all of these, because it makes no assumption about the shape of the recurrence.", }, }, { diff --git a/app/design-algorithm/fundamentals/recurrence-relations/substitution-method/content.jsx b/app/design-algorithm/fundamentals/recurrence-relations/substitution-method/content.jsx index fe5df7d..fad42b4 100755 --- a/app/design-algorithm/fundamentals/recurrence-relations/substitution-method/content.jsx +++ b/app/design-algorithm/fundamentals/recurrence-relations/substitution-method/content.jsx @@ -2,6 +2,7 @@ import DailyDSAEmbed from "@/app/components/ui/DailyDSAEmbed"; import { useTheme } from "@/app/contexts/ThemeContext"; import NewsletterEmbed from "@/app/components/ui/NewsletterEmbed"; +import BackendEngineerCard from "@/app/components/ui/BackendEngineerCard"; import InContentAd from "@/app/components/ads/InContentAd"; // The method is a loop, not a straight line: a failed proof sends you back to @@ -65,7 +66,7 @@ const MethodFlow = () => { {box(100, 14, 160, 40, "1. Guess the bound", "from a recursion tree or a known form", "#3b82f6")} {box(100, 92, 160, 40, "2. Prove it by induction", "assume for smaller n, show for n", "#3b82f6")} - {box(100, 172, 160, 40, "Done — bound proved", "with explicit c and n₀", "#10b981")} + {box(100, 172, 160, 40, "Done: bound proved", "with explicit c and n₀", "#10b981")} {/* Down arrows */} @@ -91,8 +92,8 @@ const MethodFlow = () => {
- When the algebra leaves you a term short, the guess needs strengthening — - that dashed path is the method, not a failure of it. + When the algebra leaves you a term short, the guess needs strengthening. + That dashed path is the method, not a failure of it.
); @@ -246,19 +247,19 @@ const Content = () => { const inductionParts = [ { - points: "The inductive hypothesis — the bound you are assuming.", + points: "The inductive hypothesis: the bound you are assuming.", subpoints: [ "Assume the bound holds for every input smaller than n, for example T(k) ≤ ck log k for all k < n.", ], }, { - points: "The inductive step — the algebra.", + points: "The inductive step: the algebra.", subpoints: [ "Substitute that assumption into the recurrence and show the same bound comes out for n. This is the step the method is named after.", ], }, { - points: "The base case — where the induction starts.", + points: "The base case: where the induction starts.", subpoints: [ "Show the bound holds for one or more small values of n directly, choosing c large enough to make it true.", ], @@ -269,7 +270,7 @@ const Content = () => { [ "Master Theorem", "T(n) = aT(n/b) + f(n) only", - "Almost none — three cases", + "Almost none: three cases", "A tight Θ bound, instantly", ], [ @@ -317,7 +318,7 @@ const Content = () => { { points: "Declaring victory with the wrong constant.", subpoints: [ - "Ending at (c + 1)n and calling it O(n) is the single most common error. The constant must come out unchanged — you must reach ≤ cn, not ≤ (c+1)n.", + "Ending at (c + 1)n and calling it O(n) is the single most common error. The constant must come out unchanged. You must reach ≤ cn, not ≤ (c+1)n.", ], }, { @@ -359,15 +360,15 @@ const Content = () => { }, { q: "When should I use substitution instead of the Master Theorem?", - a: "Whenever the recurrence does not have the form T(n) = aT(n/b) + f(n) with constant a and b — unequal subproblem sizes, subtractive recurrences like T(n − 1), non-constant a — or when it falls into one of the Master Theorem's gaps. Substitution works on any recurrence; the Master Theorem is faster but far narrower.", + a: "Whenever the recurrence does not have the form T(n) = aT(n/b) + f(n) with constant a and b (unequal subproblem sizes, subtractive recurrences like T(n − 1), non-constant a), or when it falls into one of the Master Theorem's gaps. Substitution works on any recurrence; the Master Theorem is faster but far narrower.", }, { q: "How do I come up with the initial guess?", - a: "Usually from a recursion tree: sketch it, sum the levels roughly, and use that as your guess. You can also match the recurrence against a familiar one, or prove loose upper and lower bounds first and tighten from both sides. The guess does not need to be inspired — it needs to be checkable.", + a: "Usually from a recursion tree: sketch it, sum the levels roughly, and use that as your guess. You can also match the recurrence against a familiar one, or prove loose upper and lower bounds first and tighten from both sides. The guess does not need to be inspired. It needs to be checkable.", }, { q: "Why does my proof fail even though the guess is right?", - a: "Almost always because the inductive hypothesis is too weak. If the algebra leaves you at cn + 1 when you needed cn, subtract a lower-order term from the hypothesis — assume T(n) ≤ cn − d instead of T(n) ≤ cn. The stronger statement is paradoxically easier to prove, because the extra −d absorbs the leftover term.", + a: "Almost always because the inductive hypothesis is too weak. If the algebra leaves you at cn + 1 when you needed cn, subtract a lower-order term from the hypothesis: assume T(n) ≤ cn − d instead of T(n) ≤ cn. The stronger statement is paradoxically easier to prove, because the extra −d absorbs the leftover term.", }, { q: "Why can the base case start at n = 2 instead of n = 1?", @@ -375,31 +376,31 @@ const Content = () => { }, { q: "Can substitution prove a lower bound as well?", - a: "Yes. The structure is identical, with ≤ replaced by ≥ throughout, and the constant chosen small enough rather than large enough. To establish Θ you prove both directions — an O bound and an Ω bound with the same function.", + a: "Yes. The structure is identical, with ≤ replaced by ≥ throughout, and the constant chosen small enough rather than large enough. To establish Θ you prove both directions: an O bound and an Ω bound with the same function.", }, ]; return (
- - + +

The substitution method solves a recurrence in two steps:{" "} guess the form of the answer, then prove it correct by - mathematical induction. The name comes from the second step — - you substitute the inductive hypothesis into the recurrence and check + mathematical induction. The name comes from the second step. + You substitute the inductive hypothesis into the recurrence and check that the same bound survives.

That structure makes it the odd one out among the three techniques. The Master Theorem and the recursion tree both derive an answer; substitution verifies one you already have. It cannot - tell you what the solution is. What it can do — and neither of the - others can — is turn a plausible answer into a proof, for a + tell you what the solution is. What it can do (and neither of the + others can), is turn a plausible answer into a proof, for a recurrence of any shape whatsoever.

@@ -434,13 +435,13 @@ const Content = () => {

One rule governs the whole exercise: the constant must come out unchanged. If you assume T(k) ≤ ck and the algebra ends at - (c + 1)n, you have not proved T(n) ≤ cn — you have proved a weaker + (c + 1)n, you have not proved T(n) ≤ cn. You have proved a weaker statement with a constant that grows at every level, which over log n levels is not a constant at all.

-
+

This is the merge sort recurrence. A recursion tree suggests O(n log n), so that is the guess. @@ -469,7 +470,7 @@ const Content = () => {

Base case. Here the induction cannot start at n = 1: the bound cn log n equals 0 when n = 1, and T(1) = 1 is not ≤ 0. This is not a - problem — asymptotic claims only need to hold for n ≥ n₀, so start at + problem: asymptotic claims only need to hold for n ≥ n₀, so start at n = 2 and n = 3 instead. With T(1) = 1, the recurrence gives T(2) = 4 and T(3) = 5. Then:

@@ -503,17 +504,17 @@ const Content = () => { />

So T(n) = Ω(n log n) with c = 1. Combined with the upper bound, - T(n) = Θ(n log n) — the same answer the Master Theorem gives + T(n) = Θ(n log n): the same answer the Master Theorem gives through Case 2, now proved from first principles.

-
+

Solve T(n) = T(n − 1) + n, with T(1) = 1. The input shrinks by subtraction, so there is no b and the Master Theorem does not apply at all. A recursion tree is a single chain costing n, n−1, n−2, …, which - sums to about n²/2 — so guess O(n²). + sums to about n²/2, so guess O(n²).

Inductive hypothesis. Assume T(k) ≤ ck² for all k < n. @@ -530,12 +531,12 @@ const Content = () => { />

Base case. T(1) = 1 ≤ c · 1² holds for c ≥ 1. Taking c = 1 - satisfies everything, so T(n) = O(n²) — which matches the exact + satisfies everything, so T(n) = O(n²), which matches the exact answer n(n+1)/2.

-
+

Suppose you guessed O(n) for the merge sort recurrence instead. Assume T(k) ≤ ck and substitute: @@ -546,7 +547,7 @@ const Content = () => { { expr: " ≤ 2 · c(n/2) + n" }, { expr: " = cn + n" }, { expr: " = (c + 1)n" }, - { expr: " ≠ ≤ cn", note: "the constant grew — the proof fails" }, + { expr: " ≠ ≤ cn", note: "the constant grew: the proof fails" }, ]} /> @@ -571,7 +572,7 @@ const Content = () => {

@@ -590,7 +591,7 @@ const Content = () => { The stronger hypothesis carries a spare −d through the induction, and that spare term absorbs the leftover +1. Since ck − d is still O(k), proving the stronger statement proves the original one. Being handed a - stronger assumption is what makes the step work — a genuinely + stronger assumption is what makes the step work: a genuinely useful trick, and one worth recognising when a proof stalls one term away from closing.

@@ -599,7 +600,7 @@ const Content = () => {

Some recurrences become familiar after a substitution of a different - kind — renaming the variable. Consider T(n) = 2T(√n) + log n, which + kind: renaming the variable. Consider T(n) = 2T(√n) + log n, which fits none of the standard patterns.

@@ -627,6 +628,14 @@ const Content = () => {

+ {/* Newsletter: inline, after the substantive sections and before the + end matter. NewsletterEmbed renders null below 768px (desktop-only + for mobile performance), so the wrapper hides at the same + breakpoint, otherwise this leaves an empty padded box on phones. */} +
+ +
+
{faqs.map((faq, index) => ( @@ -644,9 +653,9 @@ const Content = () => { = throughout, and the constant chosen small enough rather than large enough. To establish Theta you prove both directions — an O bound and an Omega bound with the same function.", + text: "Yes. The structure is identical, with <= replaced by >= throughout, and the constant chosen small enough rather than large enough. To establish Theta you prove both directions: an O bound and an Omega bound with the same function.", }, }, ], diff --git a/app/design-algorithm/layout.jsx b/app/design-algorithm/layout.jsx index 61dc955..0ea4c19 100755 --- a/app/design-algorithm/layout.jsx +++ b/app/design-algorithm/layout.jsx @@ -5,7 +5,7 @@ export const metadata = { alternates: { canonical: "/design-algorithm" }, title: 'Design & Analysis of Algorithms', description: - 'Learn the design and analysis of algorithms — asymptotic notation, time and space complexity, recurrence relations (Master Theorem, substitution, recursion tree) and amortized analysis, explained step by step.', + 'Learn the design and analysis of algorithms: asymptotic notation, time and space complexity, recurrence relations (Master Theorem, substitution, recursion tree) and amortized analysis, explained step by step.', keywords: [ 'Design and Analysis of Algorithms', 'DAA', diff --git a/bun.lock b/bun.lock index d9a7363..9da5af6 100755 --- a/bun.lock +++ b/bun.lock @@ -40,7 +40,6 @@ "react-responsive-carousel": "^3.2.23", "react-router-dom": "^7.18.2", "react-syntax-highlighter": "^16.1.1", - "recharts": "^3.10.1", "remark-gfm": "^4.0.1", "sanitize-html": "^2.17.6", }, @@ -233,14 +232,8 @@ "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], - "@reduxjs/toolkit": ["@reduxjs/toolkit@2.12.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw=="], - "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], - "@supabase/auth-helpers-nextjs": ["@supabase/auth-helpers-nextjs@0.15.0", "", { "dependencies": { "cookie": "^1.0.2" }, "peerDependencies": { "@supabase/supabase-js": "^2.76.1" } }, "sha512-VtXz3GGnxluoxks1g3SaCoYr2OZ7PgRukDl+pLWrDfD2dPDaG8hmkp5iBZsU+lmsDYALGNO2dgbymgpAfD8eCQ=="], "@supabase/auth-js": ["@supabase/auth-js@2.111.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-hbRLgyQZEX0SDyF4LYXpv94qOIQyFATfpT5SIs2V0SisHnisVRkYgiPQWzv80l4S2mO3qof78rmoH9j5zoFsYQ=="], @@ -377,24 +370,6 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], - "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], - - "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], - - "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], - - "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], - - "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], - - "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], - - "@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="], - - "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], - - "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], - "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], @@ -419,8 +394,6 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.65.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/type-utils": "8.65.0", "@typescript-eslint/utils": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.65.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA=="], @@ -599,28 +572,6 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], - - "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], - - "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], - - "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], - - "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], - - "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], - - "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], - - "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], - - "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], - - "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], - - "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], - "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], @@ -633,8 +584,6 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], - "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], @@ -695,8 +644,6 @@ "es-to-primitive": ["es-to-primitive@1.3.4", "", { "dependencies": { "es-abstract-get": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "is-callable": "^1.2.7", "is-date-object": "^1.1.0", "is-symbol": "^1.1.1" } }, "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw=="], - "es-toolkit": ["es-toolkit@1.50.0", "", {}, "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w=="], - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -735,8 +682,6 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], - "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -851,8 +796,6 @@ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "immer": ["immer@11.1.15", "", {}, "sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ=="], - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], @@ -861,8 +804,6 @@ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], - "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], - "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], @@ -1219,8 +1160,6 @@ "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], - "react-redux": ["react-redux@9.3.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g=="], - "react-responsive-carousel": ["react-responsive-carousel@3.2.23", "", { "dependencies": { "classnames": "^2.2.5", "prop-types": "^15.5.8", "react-easy-swipe": "^0.0.21" } }, "sha512-pqJLsBaKHWJhw/ItODgbVoziR2z4lpcJg+YwmRlSk4rKH32VE633mAtZZ9kDXjy4wFO+pgUZmDKPsPe1fPmHCg=="], "react-router": ["react-router@7.18.2", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg=="], @@ -1229,12 +1168,6 @@ "react-syntax-highlighter": ["react-syntax-highlighter@16.1.1", "", { "dependencies": { "@babel/runtime": "^7.28.4", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.30.0", "refractor": "^5.0.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA=="], - "recharts": ["recharts@3.10.1", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^11.1.8", "react-redux": "8.x.x || 9.x.x", "reselect": "5.2.0", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA=="], - - "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="], - - "redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="], - "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], "refractor": ["refractor@5.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/prismjs": "^1.0.0", "hastscript": "^9.0.0", "parse-entities": "^4.0.0" } }, "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw=="], @@ -1249,8 +1182,6 @@ "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], - "reselect": ["reselect@5.2.0", "", {}, "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw=="], - "resolve": ["resolve@2.0.0-next.7", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], @@ -1337,8 +1268,6 @@ "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], - "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], @@ -1387,16 +1316,12 @@ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="], - "warning": ["warning@4.0.3", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], diff --git a/package.json b/package.json index b705197..297e9f8 100755 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "react-responsive-carousel": "^3.2.23", "react-router-dom": "^7.18.2", "react-syntax-highlighter": "^16.1.1", - "recharts": "^3.10.1", "remark-gfm": "^4.0.1", "sanitize-html": "^2.17.6" },