Skip to content

Commit 793dc22

Browse files
Merge branch 'main' into feature/pascalTriangle
2 parents 8c59cbd + 06c5914 commit 793dc22

File tree

4 files changed

+292
-0
lines changed

4 files changed

+292
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
export function longestCommonSubsequenceSteps(a = "", b = "") {
2+
const m = a.length;
3+
const n = b.length;
4+
5+
// ✅ initialize DP table
6+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
7+
const steps = [];
8+
9+
const snapshot = () => dp.map(row => [...row]);
10+
11+
// ✅ filling dp table
12+
for (let i = 1; i <= m; i++) {
13+
for (let j = 1; j <= n; j++) {
14+
if (a[i - 1] === b[j - 1]) {
15+
dp[i][j] = dp[i - 1][j - 1] + 1;
16+
steps.push({
17+
dp: snapshot(),
18+
active: { i, j },
19+
match: { i, j },
20+
message: `Characters match '${a[i - 1]}' at A[${i - 1}] & B[${j - 1}] → dp[${i}][${j}] = dp[${i - 1}][${j - 1}] + 1 = ${dp[i][j]}`
21+
});
22+
} else {
23+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
24+
steps.push({
25+
dp: snapshot(),
26+
active: { i, j },
27+
message: `No match. dp[${i}][${j}] = max(dp[${i - 1}][${j}] = ${dp[i - 1][j]}, dp[${i}][${j - 1}] = ${dp[i][j - 1]}) = ${dp[i][j]}`
28+
});
29+
}
30+
}
31+
}
32+
33+
// ✅ traceback to get sequence
34+
let i = m, j = n;
35+
let lcsChars = [];
36+
const path = [];
37+
38+
while (i > 0 && j > 0) {
39+
if (a[i - 1] === b[j - 1]) {
40+
lcsChars.push(a[i - 1]);
41+
path.push({ i, j });
42+
i--;
43+
j--;
44+
} else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
45+
else j--;
46+
}
47+
48+
const finalPath = path.reverse();
49+
steps.push({
50+
dp: snapshot(),
51+
finalPath,
52+
message: `Final LCS sequence = '${lcsChars.reverse().join('')}'`,
53+
sequence: lcsChars.join('')
54+
});
55+
56+
return steps;
57+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import React, { useState, useEffect, useRef } from "react";
2+
import { longestCommonSubsequenceSteps } from "../../algorithms/dynamic-programming/longestCommonSubsequence";
3+
4+
const DPGrid = ({ dp, active, match, finalPath }) => {
5+
if (!dp || dp.length === 0) return null;
6+
7+
return (
8+
<div className="mt-4 overflow-auto">
9+
<h3 className="text-xl font-semibold mb-3 text-blue-400">DP Table</h3>
10+
<div className="inline-block border rounded-lg bg-gray-800 p-3">
11+
<table className="border-collapse">
12+
<tbody>
13+
{dp.map((row, i) => (
14+
<tr key={i}>
15+
{row.map((cell, j) => {
16+
const isActive = active && active.i === i && active.j === j;
17+
const isMatch = match && match.i === i && match.j === j;
18+
const isFinal = finalPath?.some(p => p.i === i && p.j === j);
19+
const color = isFinal
20+
? "bg-yellow-600"
21+
: isMatch
22+
? "bg-green-600"
23+
: isActive
24+
? "bg-blue-600"
25+
: "bg-gray-700";
26+
27+
return (
28+
<td
29+
key={j}
30+
className={`w-14 h-12 border text-center text-white ${color}`}
31+
>
32+
{cell}
33+
</td>
34+
);
35+
})}
36+
</tr>
37+
))}
38+
</tbody>
39+
</table>
40+
</div>
41+
</div>
42+
);
43+
};
44+
45+
const SPEEDS = { Slow: 1200, Medium: 600, Fast: 250 };
46+
47+
export default function LCSVisualizer() {
48+
const [a, setA] = useState("ABCBDAB");
49+
const [b, setB] = useState("BDCAB");
50+
const [steps, setSteps] = useState([]);
51+
const [stepIndex, setStepIndex] = useState(0);
52+
const [isPlaying, setIsPlaying] = useState(false);
53+
const [speed, setSpeed] = useState(SPEEDS.Medium);
54+
const timer = useRef(null);
55+
56+
const handleStart = () => {
57+
const s = longestCommonSubsequenceSteps(a, b);
58+
setSteps(s);
59+
setStepIndex(0);
60+
setIsPlaying(false);
61+
};
62+
63+
useEffect(() => {
64+
if (isPlaying && stepIndex < steps.length - 1) {
65+
timer.current = setInterval(() => {
66+
setStepIndex(i => i + 1);
67+
}, speed);
68+
} else clearInterval(timer.current);
69+
70+
return () => clearInterval(timer.current);
71+
}, [isPlaying, stepIndex, steps.length, speed]);
72+
73+
const togglePlay = () => {
74+
if (stepIndex === steps.length - 1) setStepIndex(0);
75+
setIsPlaying(!isPlaying);
76+
};
77+
78+
const current = steps[stepIndex] || {};
79+
const finalLCS = stepIndex === steps.length - 1 ? current.sequence : "";
80+
81+
return (
82+
<div className="p-6 min-h-screen bg-gray-900 text-gray-100 font-sans">
83+
<div className="max-w-5xl mx-auto">
84+
<h1 className="text-4xl font-extrabold mb-8 text-indigo-400 text-center">
85+
Longest Common Subsequence (LCS)
86+
</h1>
87+
88+
<div className="mb-8 p-4 rounded-xl bg-gray-800 border border-gray-700">
89+
<p className="text-gray-300">
90+
This visualizer shows how the DP table for LCS is filled step by step and how the final sequence is reconstructed.
91+
</p>
92+
</div>
93+
94+
<div className="flex flex-wrap justify-center items-center gap-5 mb-8 p-6 rounded-xl bg-gray-800 border border-gray-700">
95+
<div className="w-full md:w-1/3">
96+
<label className="text-gray-300">String A:</label>
97+
<input
98+
value={a}
99+
onChange={e => setA(e.target.value)}
100+
className="w-full mt-2 p-2 rounded-lg bg-gray-700 border border-gray-600 text-white"
101+
/>
102+
</div>
103+
104+
<div className="w-full md:w-1/3">
105+
<label className="text-gray-300">String B:</label>
106+
<input
107+
value={b}
108+
onChange={e => setB(e.target.value)}
109+
className="w-full mt-2 p-2 rounded-lg bg-gray-700 border border-gray-600 text-white"
110+
/>
111+
</div>
112+
113+
<button
114+
onClick={handleStart}
115+
className="bg-indigo-600 hover:bg-indigo-700 text-white font-bold px-6 py-3 rounded-xl"
116+
>
117+
Start Visualization
118+
</button>
119+
</div>
120+
121+
{steps.length > 0 ? (
122+
<>
123+
<div className="flex flex-wrap justify-between items-center mb-6 p-4 bg-gray-800 border border-gray-700 rounded-xl">
124+
<button
125+
onClick={togglePlay}
126+
className={`px-5 py-2 rounded-lg font-semibold ${
127+
isPlaying ? "bg-red-600" : "bg-green-600"
128+
} text-white`}
129+
>
130+
{isPlaying ? "Pause ⏸️" : "Play ▶️"}
131+
</button>
132+
133+
<div className="flex gap-2">
134+
<button
135+
onClick={() => setStepIndex(i => Math.max(0, i - 1))}
136+
className={`px-3 py-2 rounded-lg font-semibold ${
137+
stepIndex > 0
138+
? "bg-purple-600 text-white"
139+
: "bg-gray-600 text-gray-400"
140+
}`}
141+
>
142+
&lt; Prev
143+
</button>
144+
<button
145+
onClick={() =>
146+
setStepIndex(i =>
147+
i < steps.length - 1 ? i + 1 : steps.length - 1
148+
)
149+
}
150+
className={`px-3 py-2 rounded-lg font-semibold ${
151+
stepIndex < steps.length - 1
152+
? "bg-purple-600 text-white"
153+
: "bg-gray-600 text-gray-400"
154+
}`}
155+
>
156+
Next &gt;
157+
</button>
158+
</div>
159+
160+
<div className="flex items-center gap-2">
161+
<label className="text-gray-300">Speed:</label>
162+
<select
163+
value={speed}
164+
onChange={e => setSpeed(Number(e.target.value))}
165+
className="p-2 rounded-lg bg-gray-700 text-white border border-gray-600"
166+
>
167+
{Object.entries(SPEEDS).map(([label, ms]) => (
168+
<option key={label} value={ms}>
169+
{label}
170+
</option>
171+
))}
172+
</select>
173+
</div>
174+
</div>
175+
176+
<div className="text-center mb-4">
177+
<p className="text-2xl font-bold text-yellow-400">
178+
Step {stepIndex + 1} / {steps.length}
179+
</p>
180+
</div>
181+
182+
<div className="border border-gray-700 p-6 rounded-xl bg-gray-800 shadow-2xl">
183+
<div className="mb-6 p-4 rounded-lg bg-gray-700 border-l-4 border-teal-400">
184+
<p className="text-teal-400 font-semibold uppercase">
185+
Current Action
186+
</p>
187+
<p className="text-lg mt-2 text-gray-200 leading-relaxed">
188+
{current.message || "Processing..."}
189+
</p>
190+
</div>
191+
192+
{current.dp && (
193+
<DPGrid
194+
dp={current.dp}
195+
active={current.active}
196+
match={current.match}
197+
finalPath={current.finalPath}
198+
/>
199+
)}
200+
201+
{finalLCS && (
202+
<div className="mt-8 p-5 rounded-xl bg-green-900 border border-green-700 text-center shadow-lg">
203+
<p className="text-green-400 text-2xl font-extrabold">
204+
🎉 Final LCS ={" "}
205+
<span className="text-green-200 text-3xl">{finalLCS}</span>
206+
</p>
207+
</div>
208+
)}
209+
</div>
210+
</>
211+
) : (
212+
<div className="text-center p-12 bg-gray-800 rounded-xl text-gray-400 text-xl border border-gray-700">
213+
<p className="mb-4">Welcome to the LCS Visualizer!</p>
214+
<p>Enter two strings and click “Start Visualization”.</p>
215+
</div>
216+
)}
217+
</div>
218+
</div>
219+
);
220+
}

src/pages/dynamic-programming/DyanmicProgrammingPage.jsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import MatrixChainMultiplication from "./MatrixChainMultiplication";
55
import FibonacciSequence from "./FibonacciSequence";
66
import Knapsack from "./Knapsack";
77
import PascalTriangle from "./PascalTriangle";
8+
import LCSPage from "./LCS";
89

910
export default function DynamicProgrammingPage() {
1011
const [selectedAlgo, setSelectedAlgo] = useState("");
@@ -42,6 +43,12 @@ export default function DynamicProgrammingPage() {
4243
<PascalTriangle />
4344
</div>
4445
);
46+
case "LongestCommonSubsequence":
47+
return (
48+
<div className="md:w-full w-screen overflow-clip p-2">
49+
<LCSPage />
50+
</div>
51+
);
4552
default:
4653
return (
4754
<div className="flex flex-col items-center justify-center text-center p-6 min-h-screen bg-gray-950">
@@ -105,6 +112,8 @@ export default function DynamicProgrammingPage() {
105112
<option value="Fibonacci">Fibonacci Sequence</option>
106113
<option value="Knapsack">Knapsack</option>
107114
<option value="PascalTriangle">Pascal Triangle</option> {/* ✅ added */}
115+
<option value="Knapsack">Knapsack</option>
116+
<option value="LongestCommonSubsequence">Longest Common Subsequence</option>
108117
</select>
109118

110119
<button
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import React from "react";
2+
import LCSVisualizer from "@/components/dynamic-programming/LCSVisualizer";
3+
4+
export default function LCSPage() {
5+
return <LCSVisualizer />;
6+
}

0 commit comments

Comments
 (0)