Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/algorithms/dynamic-programming/longestIncreasingSubsequence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export function longestIncreasingSubsequenceSteps(arr = []) {
const n = arr.length;
const dp = Array(n).fill(1);
const parent = Array(n).fill(-1);
const steps = [];

const snapshot = () => [...dp];

// Fill dp array
for (let i = 0; i < n; i++) {
for (let j = 0; j < i; j++) {
if (arr[j] < arr[i] && dp[j] + 1 > dp[i]) {
parent[i] = j;
dp[i] = dp[j] + 1;
steps.push({
dp: snapshot(),
active: { i, j },
match: { i, j },
message: `arr[${j}] (${arr[j]}) < arr[${i}] (${arr[i]}) → dp[${i}] = dp[${j}] + 1 = ${dp[i]}`
});
} else {
steps.push({
dp: snapshot(),
active: { i, j },
message: `arr[${j}] (${arr[j]}) >= arr[${i}] (${arr[i]}) → no update, dp[${i}] remains ${dp[i]}`
});
}
}
}

// Find LIS length and reconstruct sequence
let lisLen = 0, lisEnd = 0;
for (let i = 0; i < n; i++) {
if (dp[i] > lisLen) {
lisLen = dp[i];
lisEnd = i;
}
}

const sequence = [];
let t = lisEnd;
while (t !== -1) {
sequence.push(arr[t]);
t = parent[t];
}
sequence.reverse();

steps.push({
dp: snapshot(),
message: `Final LIS sequence = [${sequence.join(', ')}], length = ${lisLen}`,
sequence,
});

return steps;
}
191 changes: 191 additions & 0 deletions src/components/dynamic-programming/LISVisualizer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import React, { useState, useEffect, useRef } from "react";
import { longestIncreasingSubsequenceSteps } from "../../algorithms/dynamic-programming/longestIncreasingSubsequence";

const DPArray = ({ dp, active }) => {
if (!dp || dp.length === 0) return null;

return (
<div className="mt-4 overflow-auto">
<h3 className="text-xl font-semibold mb-3 text-blue-400">
DP Array (LIS Lengths)
</h3>
<div className="inline-flex gap-2 border rounded-lg bg-gray-800 p-3">
{dp.map((val, i) => {
const isActive = active && active.i === i;
const color = isActive ? "bg-blue-600" : "bg-gray-700";
return (
<div
key={i}
className={`w-14 h-12 flex items-center justify-center text-white font-semibold border ${color} rounded-md`}
>
{val}
</div>
);
})}
</div>
</div>
);
};

const SPEEDS = { Slow: 1200, Medium: 600, Fast: 250 };

export default function LISVisualizer() {
const [input, setInput] = useState("10,22,9,33,21,50,41,60");
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 arr = input
.split(",")
.map(x => parseInt(x.trim()))
.filter(x => !isNaN(x));
const s = longestIncreasingSubsequenceSteps(arr);
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 finalLIS =
stepIndex === steps.length - 1 ? current.sequence?.join(", ") : "";

return (
<div className="p-6 min-h-screen bg-gray-900 text-gray-100 font-sans">
<div className="max-w-5xl mx-auto">
<h1 className="text-4xl font-extrabold mb-8 text-indigo-400 text-center">
Longest Increasing Subsequence (LIS)
</h1>

<div className="mb-8 p-4 rounded-xl bg-gray-800 border border-gray-700">
<p className="text-gray-300">
This visualizer demonstrates how the LIS DP array is updated
step-by-step and how the final increasing subsequence is derived.
</p>
</div>

<div className="flex flex-wrap justify-center items-center gap-5 mb-8 p-6 rounded-xl bg-gray-800 border border-gray-700">
<div className="w-full md:w-2/3">
<label className="text-gray-300">Input Array (comma separated):</label>
<input
value={input}
onChange={e => setInput(e.target.value)}
className="w-full mt-2 p-2 rounded-lg bg-gray-700 border border-gray-600 text-white"
/>
</div>

<button
onClick={handleStart}
className="bg-indigo-600 hover:bg-indigo-700 text-white font-bold px-6 py-3 rounded-xl"
>
Start Visualization
</button>
</div>

{steps.length > 0 ? (
<>
<div className="flex flex-wrap justify-between items-center mb-6 p-4 bg-gray-800 border border-gray-700 rounded-xl">
<button
onClick={togglePlay}
className={`px-5 py-2 rounded-lg font-semibold ${isPlaying ? "bg-red-600" : "bg-green-600"
} text-white`}
>
{isPlaying ? "Pause ⏸️" : "Play ▶️"}
</button>

<div className="flex gap-2">
<button
onClick={() => setStepIndex(i => Math.max(0, i - 1))}
className={`px-3 py-2 rounded-lg font-semibold ${stepIndex > 0
? "bg-purple-600 text-white"
: "bg-gray-600 text-gray-400"
}`}
>
&lt; Prev
</button>
<button
onClick={() =>
setStepIndex(i =>
i < steps.length - 1 ? i + 1 : steps.length - 1
)
}
className={`px-3 py-2 rounded-lg font-semibold ${stepIndex < steps.length - 1
? "bg-purple-600 text-white"
: "bg-gray-600 text-gray-400"
}`}
>
Next &gt;
</button>
</div>

<div className="flex items-center gap-2">
<label className="text-gray-300">Speed:</label>
<select
value={speed}
onChange={e => setSpeed(Number(e.target.value))}
className="p-2 rounded-lg bg-gray-700 text-white border border-gray-600"
>
{Object.entries(SPEEDS).map(([label, ms]) => (
<option key={label} value={ms}>
{label}
</option>
))}
</select>
</div>
</div>

<div className="text-center mb-4">
<p className="text-2xl font-bold text-yellow-400">
Step {stepIndex + 1} / {steps.length}
</p>
</div>

<div className="border border-gray-700 p-6 rounded-xl bg-gray-800 shadow-2xl">
<div className="mb-6 p-4 rounded-lg bg-gray-700 border-l-4 border-teal-400">
<p className="text-teal-400 font-semibold uppercase">
Current Action
</p>
<p className="text-lg mt-2 text-gray-200 leading-relaxed">
{current.message || "Processing..."}
</p>
</div>

{current.dp && <DPArray dp={current.dp} active={current.active} />}

{finalLIS && (
<div className="mt-8 p-5 rounded-xl bg-green-900 border border-green-700 text-center shadow-lg">
<p className="text-green-400 text-2xl font-extrabold">
Final LIS ={" "}
<span className="text-green-200 text-3xl">{finalLIS}</span>
</p>
</div>
)}
</div>
</>
) : (
<div className="text-center p-12 bg-gray-800 rounded-xl text-gray-400 text-xl border border-gray-700">
<p className="mb-4">Welcome to the LIS Visualizer!</p>
<p>Enter an array and click “Start Visualization”.</p>
</div>
)}
</div>
</div>
);
}
15 changes: 12 additions & 3 deletions src/pages/dynamic-programming/DyanmicProgrammingPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import FibonacciSequence from "./FibonacciSequence";
import Knapsack from "./Knapsack";
import PascalTriangle from "./PascalTriangle";
import LCSPage from "./LCS";
import CoinChange from "./CoinChange"; // ✅ Added import
import LISPage from "./LIS";
import CoinChange from "./CoinChange";

import RodCutting from "./RodCutting";

export default function DynamicProgrammingPage() {
Expand Down Expand Up @@ -51,7 +53,13 @@ export default function DynamicProgrammingPage() {
<LCSPage />
</div>
);
case "CoinChange": // ✅ Added new algorithm case
case "LongestIncreasingSubsequence":
return (
<div className="md:w-full w-screen overflow-clip p-2">
<LISPage />
</div>
);
case "CoinChange":
return (
<div className="md:w-full w-screen overflow-clip p-2">
<CoinChange />
Expand Down Expand Up @@ -127,7 +135,8 @@ export default function DynamicProgrammingPage() {
<option value="Knapsack">Knapsack</option>
<option value="PascalTriangle">Pascal Triangle</option>
<option value="LongestCommonSubsequence">Longest Common Subsequence</option>
<option value="CoinChange">Coin Change</option> {/* ✅ Added */}
<option value="LongestIncreasingSubsequence">Longest Increasing Subsequence</option>
<option value="CoinChange">Coin Change</option>
<option value="RodCutting">Rod Cutting</option>
</select>

Expand Down
6 changes: 6 additions & 0 deletions src/pages/dynamic-programming/LIS.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from "react";
import LISVisualizer from "@/components/dynamic-programming/LISVisualizer";

export default function LISPage() {
return <LISVisualizer />;
}
Loading