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
116 changes: 116 additions & 0 deletions src/algorithms/dynamic-programming/coinChange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
export function coinChangeTabulation(coins = [], amount = 0) {
// dp dimensions: (n + 1) x (amount + 1)
const n = coins.length;
const dp = Array.from({ length: n + 1 }, () =>
new Array(amount + 1).fill(Infinity)
);

const steps = [];

// Base case: dp[i][0] = 0 for all i
for (let i = 0; i <= n; i++) {
dp[i][0] = 0;
steps.push({
type: "base_case",
row: i,
col: 0,
value: 0,
dp: dp.map((r) => [...r]),
message: `Base case: dp[${i}][0] = 0 (0 coins needed to make amount 0).`,
});
}

// Fill table row by row: i = 1..n (using first i coins), j = 1..amount
for (let i = 1; i <= n; i++) {
const coin = coins[i - 1];
for (let j = 1; j <= amount; j++) {
// Highlight cell selection
steps.push({
type: "select_cell",
row: i,
col: j,
coin,
dp: dp.map((r) => [...r]),
message: `Computing dp[${i}][${j}] considering coin ${coin}.`,
});

// Option 1: don't use current coin => dp[i-1][j]
const option1 = dp[i - 1][j];
steps.push({
type: "option_without",
row: i,
col: j,
coin,
optionValue: option1,
dp: dp.map((r) => [...r]),
message: `Option 1 (without coin ${coin}): dp[${i - 1}][${j}] = ${option1 === Infinity ? "∞" : option1}.`,
});

// Option 2: use current coin if j >= coin => dp[i][j-coin] + 1
let option2 = Infinity;
if (j - coin >= 0) {
option2 = dp[i][j - coin] === Infinity ? Infinity : dp[i][j - coin] + 1;
steps.push({
type: "option_with",
row: i,
col: j,
coin,
optionValue:
option2 === Infinity ? "∞" : option2,
dp: dp.map((r) => [...r]),
message: `Option 2 (use coin ${coin}): dp[${i}][${j - coin}] ${dp[i][j - coin] === Infinity ? "= ∞" : `= ${dp[i][j - coin]}`
} → +1 => ${option2 === Infinity ? "∞" : option2}.`,
});
} else {
steps.push({
type: "option_with_unavailable",
row: i,
col: j,
coin,
optionValue: "N/A",
dp: dp.map((r) => [...r]),
message: `Option 2 (use coin ${coin}) not possible since ${coin} > ${j}.`,
});
}

// Take min and store
const chosen = Math.min(option1, option2);
dp[i][j] = chosen;

steps.push({
type: "take_min",
row: i,
col: j,
coin,
chosen: chosen === Infinity ? "∞" : chosen,
option1: option1 === Infinity ? "∞" : option1,
option2: option2 === Infinity ? "∞" : option2,
dp: dp.map((r) => [...r]),
message: `dp[${i}][${j}] = min(${option1 === Infinity ? "∞" : option1}, ${option2 === Infinity ? "∞" : option2}) = ${chosen === Infinity ? "∞" : chosen}.`,
});
}
}

const result = dp[n][amount] === Infinity ? -1 : dp[n][amount];

// Prepare visual steps (trim large dp values to Infinity string for clarity)
const visualSteps = steps.map((step) => ({
array: step.dp.map((r) => r.map((v) => (v === Infinity ? Infinity : v))),
currentPosition: { row: step.row, col: step.col },
coin: step.coin,
message: step.message,
type: step.type,
extra: {
option1: step.option1,
option2: step.option2,
chosen: step.chosen,
optionValue: step.optionValue,
},
}));

return {
steps: visualSteps,
result,
dpFinal: dp.map((r) => r.map((v) => (v === Infinity ? Infinity : v))),
};
}
209 changes: 209 additions & 0 deletions src/components/dynamic-programming/coinChangeVisualizer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import React, { useState, useEffect, useRef } from "react";

export default function CoinChangeVisualizer() {
const [coins, setCoins] = useState([1, 2, 5]);
const [amount, setAmount] = useState(11);
const [speed, setSpeed] = useState(500);
const [dp, setDp] = useState([]);
const [steps, setSteps] = useState([]);
const [currentStep, setCurrentStep] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const intervalRef = useRef(null);

// --- Algorithm Logic ---
const generateSteps = (coins, amount) => {
const dpTable = Array(coins.length + 1)
.fill(null)
.map(() => Array(amount + 1).fill(Infinity));
for (let i = 0; i <= coins.length; i++) dpTable[i][0] = 0;

const stepsList = [];
for (let i = 1; i <= coins.length; i++) {
for (let j = 1; j <= amount; j++) {
const withoutCoin = dpTable[i - 1][j];
const withCoin =
j >= coins[i - 1] ? dpTable[i][j - coins[i - 1]] + 1 : Infinity;
dpTable[i][j] = Math.min(withoutCoin, withCoin);

stepsList.push({
i,
j,
coin: coins[i - 1],
withoutCoin,
withCoin,
result: dpTable[i][j],
dpSnapshot: dpTable.map((r) => [...r]),
});
}
}
return stepsList;
};

// --- Visualization Control ---
const startVisualization = () => {
const newSteps = generateSteps(coins, amount);
setSteps(newSteps);
setDp(newSteps[0].dpSnapshot);
setCurrentStep(0);
setIsRunning(true);
};

useEffect(() => {
if (isRunning && steps.length > 0) {
intervalRef.current = setInterval(() => {
setCurrentStep((prev) => {
if (prev >= steps.length - 1) {
clearInterval(intervalRef.current);
setIsRunning(false);
return prev;
}
return prev + 1;
});
}, speed);
}
return () => clearInterval(intervalRef.current);
}, [isRunning, steps, speed]);

useEffect(() => {
if (steps[currentStep]) setDp(steps[currentStep].dpSnapshot);
}, [currentStep, steps]);

const current = steps[currentStep];

// --- Color Logic ---
const getCellColor = (i, j, cell) => {
if (cell === 0) return "bg-green-600 text-white font-bold";
if (current && current.i === i && current.j === j)
return "bg-yellow-400 text-black animate-pulse shadow-lg shadow-yellow-400/40";
if (i === coins.length && j === amount)
return "bg-purple-600 text-white font-bold shadow-md";
if (cell === Infinity) return "bg-gray-800 text-gray-400";
return "bg-blue-600/70 text-white";
};

return (
<div className="p-4 md:p-8 bg-gray-950 min-h-screen text-white font-sans">
<h1 className="text-3xl font-bold mb-8 text-center text-indigo-400">
Coin Change Visualizer (Dynamic Programming)
</h1>

{/* Input Controls */}
<div className="flex flex-wrap justify-center gap-4 mb-8">
<input
type="text"
value={coins.join(",")}
onChange={(e) => setCoins(e.target.value.split(",").map(Number))}
placeholder="Coins (e.g., 1,2,5)"
className="p-2 rounded-md bg-gray-800 border border-gray-700 text-center w-48"
/>
<input
type="number"
value={amount}
onChange={(e) => setAmount(Number(e.target.value))}
placeholder="Amount"
className="p-2 rounded-md bg-gray-800 border border-gray-700 text-center w-32"
/>
<input
type="range"
min="200"
max="1500"
value={speed}
onChange={(e) => setSpeed(Number(e.target.value))}
className="w-40 accent-indigo-500"
/>
<button
onClick={startVisualization}
className="px-4 py-2 bg-indigo-600 rounded-md hover:bg-indigo-500 transition"
>
Start
</button>
<button
onClick={() => {
setIsRunning(false);
clearInterval(intervalRef.current);
setSteps([]);
setDp([]);
}}
className="px-4 py-2 bg-gray-700 rounded-md hover:bg-gray-600 transition"
>
Reset
</button>
</div>

{/* DP Table */}
<div className="overflow-x-auto flex justify-center mb-10">
<table className="border-separate border-spacing-1 text-center">
<thead>
<tr>
<th className="p-2 bg-gray-800 border border-gray-700 rounded-md text-gray-300">
Coins ↓ / Amount →
</th>
{Array.from({ length: amount + 1 }, (_, j) => (
<th
key={j}
className="p-2 bg-gray-800 border border-gray-700 rounded-md text-sm"
>
{j}
</th>
))}
</tr>
</thead>
<tbody>
{dp.map((row, i) => (
<tr key={i} className="even:bg-gray-900/40">
<td className="px-3 py-2 bg-gray-800 border border-gray-700 rounded-md text-sm text-gray-300">
{i === 0 ? "∅" : `[${coins[i - 1]}]`}
</td>
{row.map((cell, j) => (
<td
key={j}
className={`w-12 h-12 border border-gray-700 rounded-md text-center transition-all duration-300 ${getCellColor(
i,
j,
cell
)}`}
>
{cell === Infinity ? "∞" : cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>

{/* Explanation Panel */}
{current && (
<div className="text-center bg-gray-900 border border-gray-700 rounded-xl p-6 shadow-lg max-w-2xl mx-auto">
<p className="text-indigo-300 font-semibold mb-2">
Step {currentStep + 1} / {steps.length}
</p>
<p className="text-gray-200">
Computing{" "}
<span className="text-yellow-400 font-medium">
dp[{current.i}][{current.j}]
</span>{" "}
using coin{" "}
<span className="text-cyan-400 font-medium">{current.coin}</span>
</p>
<p className="mt-3 text-gray-300">
Without coin ={" "}
<span className="text-red-400">
{current.withoutCoin === Infinity ? "∞" : current.withoutCoin}
</span>{" "}
| With coin ={" "}
<span className="text-green-400">
{current.withCoin === Infinity ? "∞" : current.withCoin}
</span>
</p>
<p className="mt-3 text-green-400 font-semibold">
dp[{current.i}][{current.j}] = min(
{current.withoutCoin === Infinity ? "∞" : current.withoutCoin},{" "}
{current.withCoin === Infinity ? "∞" : current.withCoin}) ={" "}
{current.result === Infinity ? "∞" : current.result}
</p>
</div>
)}
</div>
);
}
7 changes: 7 additions & 0 deletions src/pages/dynamic-programming/CoinChange.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// src/pages/dynamic-programming/CoinChange.jsx
import React from "react";
import CoinChangeVisualizer from "../../components/dynamic-programming/CoinChangeVisualizer";

export default function CoinChange() {
return <CoinChangeVisualizer />;
}
19 changes: 13 additions & 6 deletions src/pages/dynamic-programming/DyanmicProgrammingPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import Levenshtein from "./Levenshtein";
import MatrixChainMultiplication from "./MatrixChainMultiplication";
import FibonacciSequence from "./FibonacciSequence";
import Knapsack from "./Knapsack";
import PascalTriangle from "./PascalTriangle";
import PascalTriangle from "./PascalTriangle";
import LCSPage from "./LCS";
import CoinChange from "./CoinChange"; // ✅ Added import

export default function DynamicProgrammingPage() {
const [selectedAlgo, setSelectedAlgo] = useState("");
Expand Down Expand Up @@ -37,7 +38,7 @@ export default function DynamicProgrammingPage() {
<Knapsack />
</div>
);
case "PascalTriangle":
case "PascalTriangle":
return (
<div className="md:w-full w-screen overflow-clip p-2">
<PascalTriangle />
Expand All @@ -49,6 +50,12 @@ export default function DynamicProgrammingPage() {
<LCSPage />
</div>
);
case "CoinChange": // ✅ Added new algorithm case
return (
<div className="md:w-full w-screen overflow-clip p-2">
<CoinChange />
</div>
);
default:
return (
<div className="flex flex-col items-center justify-center text-center p-6 min-h-screen bg-gray-950">
Expand All @@ -65,7 +72,7 @@ export default function DynamicProgrammingPage() {
Understand how overlapping subproblems and optimal
substructures work in real time. Visualize table-filling,
recursion trees, and memoization flow with stunning
animations! ⚙️🧩
animations!
</p>
</div>
<div className="flex items-center justify-center gap-6">
Expand Down Expand Up @@ -111,9 +118,9 @@ export default function DynamicProgrammingPage() {
<option value="MatrixChainMultiplication">Matrix Chain Multiplication</option>
<option value="Fibonacci">Fibonacci Sequence</option>
<option value="Knapsack">Knapsack</option>
<option value="PascalTriangle">Pascal Triangle</option> {/* ✅ added */}
<option value="Knapsack">Knapsack</option>
<option value="LongestCommonSubsequence">Longest Common Subsequence</option>
<option value="PascalTriangle">Pascal Triangle</option>
<option value="LongestCommonSubsequence">Longest Common Subsequence</option>
<option value="CoinChange">Coin Change</option> {/* ✅ Added */}
</select>

<button
Expand Down
Loading