From 0b3261f8a5977f59162cff783e72ba556c210c98 Mon Sep 17 00:00:00 2001 From: shivam7147 Date: Thu, 30 Oct 2025 13:20:34 +0530 Subject: [PATCH 1/2] feat : LCS Visualizer --- .../longestCommonSubsequence.js | 57 +++++ .../dynamic-programming/LCSVisualizer.jsx | 220 ++++++++++++++++++ .../DyanmicProgrammingPage.jsx | 8 + src/pages/dynamic-programming/LCS.jsx | 6 + 4 files changed, 291 insertions(+) create mode 100644 src/algorithms/dynamic-programming/longestCommonSubsequence.js create mode 100644 src/components/dynamic-programming/LCSVisualizer.jsx create mode 100644 src/pages/dynamic-programming/LCS.jsx diff --git a/src/algorithms/dynamic-programming/longestCommonSubsequence.js b/src/algorithms/dynamic-programming/longestCommonSubsequence.js new file mode 100644 index 0000000..f12db5a --- /dev/null +++ b/src/algorithms/dynamic-programming/longestCommonSubsequence.js @@ -0,0 +1,57 @@ +export function longestCommonSubsequenceSteps(a = "", b = "") { + const m = a.length; + const n = b.length; + + // ✅ initialize DP table + const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0)); + const steps = []; + + const snapshot = () => dp.map(row => [...row]); + + // ✅ filling dp table + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + if (a[i - 1] === b[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + steps.push({ + dp: snapshot(), + active: { i, j }, + match: { i, j }, + 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]}` + }); + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + steps.push({ + dp: snapshot(), + active: { i, j }, + 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]}` + }); + } + } + } + + // ✅ traceback to get sequence + let i = m, j = n; + let lcsChars = []; + const path = []; + + while (i > 0 && j > 0) { + if (a[i - 1] === b[j - 1]) { + lcsChars.push(a[i - 1]); + path.push({ i, j }); + i--; + j--; + } else if (dp[i - 1][j] >= dp[i][j - 1]) i--; + else j--; + } + + const finalPath = path.reverse(); + steps.push({ + dp: snapshot(), + finalPath, + message: `Final LCS sequence = '${lcsChars.reverse().join('')}'`, + sequence: lcsChars.join('') + }); + + return steps; +} diff --git a/src/components/dynamic-programming/LCSVisualizer.jsx b/src/components/dynamic-programming/LCSVisualizer.jsx new file mode 100644 index 0000000..dd8ea78 --- /dev/null +++ b/src/components/dynamic-programming/LCSVisualizer.jsx @@ -0,0 +1,220 @@ +import React, { useState, useEffect, useRef } from "react"; +import { longestCommonSubsequenceSteps } from "../../algorithms/dynamic-programming/longestCommonSubsequence"; + +const DPGrid = ({ dp, active, match, finalPath }) => { + if (!dp || dp.length === 0) return null; + + return ( +
+

DP Table

+
+ + + {dp.map((row, i) => ( + + {row.map((cell, j) => { + const isActive = active && active.i === i && active.j === j; + const isMatch = match && match.i === i && match.j === j; + const isFinal = finalPath?.some(p => p.i === i && p.j === j); + const color = isFinal + ? "bg-yellow-600" + : isMatch + ? "bg-green-600" + : isActive + ? "bg-blue-600" + : "bg-gray-700"; + + return ( + + ); + })} + + ))} + +
+ {cell} +
+
+
+ ); +}; + +const SPEEDS = { Slow: 1200, Medium: 600, Fast: 250 }; + +export default function LCSVisualizer() { + const [a, setA] = useState("ABCBDAB"); + const [b, setB] = useState("BDCAB"); + const [steps, setSteps] = useState([]); + const [stepIndex, setStepIndex] = useState(0); + const [isPlaying, setIsPlaying] = useState(false); + const [speed, setSpeed] = useState(SPEEDS.Medium); + const timer = useRef(null); + + const handleStart = () => { + const s = longestCommonSubsequenceSteps(a, b); + setSteps(s); + setStepIndex(0); + setIsPlaying(false); + }; + + useEffect(() => { + if (isPlaying && stepIndex < steps.length - 1) { + timer.current = setInterval(() => { + setStepIndex(i => i + 1); + }, speed); + } else clearInterval(timer.current); + + return () => clearInterval(timer.current); + }, [isPlaying, stepIndex, steps.length, speed]); + + const togglePlay = () => { + if (stepIndex === steps.length - 1) setStepIndex(0); + setIsPlaying(!isPlaying); + }; + + const current = steps[stepIndex] || {}; + const finalLCS = stepIndex === steps.length - 1 ? current.sequence : ""; + + return ( +
+
+

+ Longest Common Subsequence (LCS) +

+ +
+

+ This visualizer shows how the DP table for LCS is filled step by step and how the final sequence is reconstructed. +

+
+ +
+
+ + setA(e.target.value)} + className="w-full mt-2 p-2 rounded-lg bg-gray-700 border border-gray-600 text-white" + /> +
+ +
+ + setB(e.target.value)} + className="w-full mt-2 p-2 rounded-lg bg-gray-700 border border-gray-600 text-white" + /> +
+ + +
+ + {steps.length > 0 ? ( + <> +
+ + +
+ + +
+ +
+ + +
+
+ +
+

+ Step {stepIndex + 1} / {steps.length} +

+
+ +
+
+

+ Current Action +

+

+ {current.message || "Processing..."} +

+
+ + {current.dp && ( + + )} + + {finalLCS && ( +
+

+ 🎉 Final LCS ={" "} + {finalLCS} +

+
+ )} +
+ + ) : ( +
+

Welcome to the LCS Visualizer!

+

Enter two strings and click “Start Visualization”.

+
+ )} +
+
+ ); +} diff --git a/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx b/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx index 59664a7..4852b78 100644 --- a/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx +++ b/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx @@ -4,6 +4,7 @@ import Levenshtein from "./Levenshtein"; import MatrixChainMultiplication from "./MatrixChainMultiplication"; import FibonacciSequence from "./FibonacciSequence"; import Knapsack from "./Knapsack"; +import LCSPage from "./LCS"; export default function DynamicProgrammingPage() { const [selectedAlgo, setSelectedAlgo] = useState(""); @@ -35,6 +36,12 @@ export default function DynamicProgrammingPage() { ); + case "LongestCommonSubsequence": + return ( +
+ +
+ ); default: return (
@@ -97,6 +104,7 @@ export default function DynamicProgrammingPage() { +