diff --git a/src/algorithms/dynamic-programming/coinChange.js b/src/algorithms/dynamic-programming/coinChange.js new file mode 100644 index 0000000..5fe677d --- /dev/null +++ b/src/algorithms/dynamic-programming/coinChange.js @@ -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))), + }; +} diff --git a/src/components/dynamic-programming/coinChangeVisualizer.jsx b/src/components/dynamic-programming/coinChangeVisualizer.jsx new file mode 100644 index 0000000..fa62a57 --- /dev/null +++ b/src/components/dynamic-programming/coinChangeVisualizer.jsx @@ -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 ( +
+

+ Coin Change Visualizer (Dynamic Programming) +

+ + {/* Input Controls */} +
+ 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" + /> + setAmount(Number(e.target.value))} + placeholder="Amount" + className="p-2 rounded-md bg-gray-800 border border-gray-700 text-center w-32" + /> + setSpeed(Number(e.target.value))} + className="w-40 accent-indigo-500" + /> + + +
+ + {/* DP Table */} +
+ + + + + {Array.from({ length: amount + 1 }, (_, j) => ( + + ))} + + + + {dp.map((row, i) => ( + + + {row.map((cell, j) => ( + + ))} + + ))} + +
+ Coins ↓ / Amount → + + {j} +
+ {i === 0 ? "∅" : `[${coins[i - 1]}]`} + + {cell === Infinity ? "∞" : cell} +
+
+ + {/* Explanation Panel */} + {current && ( +
+

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

+

+ Computing{" "} + + dp[{current.i}][{current.j}] + {" "} + using coin{" "} + {current.coin} +

+

+ Without coin ={" "} + + {current.withoutCoin === Infinity ? "∞" : current.withoutCoin} + {" "} + | With coin ={" "} + + {current.withCoin === Infinity ? "∞" : current.withCoin} + +

+

+ dp[{current.i}][{current.j}] = min( + {current.withoutCoin === Infinity ? "∞" : current.withoutCoin},{" "} + {current.withCoin === Infinity ? "∞" : current.withCoin}) ={" "} + {current.result === Infinity ? "∞" : current.result} +

+
+ )} +
+ ); +} diff --git a/src/pages/dynamic-programming/CoinChange.jsx b/src/pages/dynamic-programming/CoinChange.jsx new file mode 100644 index 0000000..32e2abe --- /dev/null +++ b/src/pages/dynamic-programming/CoinChange.jsx @@ -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 ; +} diff --git a/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx b/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx index cb44ce0..13744ed 100644 --- a/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx +++ b/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx @@ -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(""); @@ -37,7 +38,7 @@ export default function DynamicProgrammingPage() { ); - case "PascalTriangle": + case "PascalTriangle": return (
@@ -49,6 +50,12 @@ export default function DynamicProgrammingPage() {
); + case "CoinChange": // ✅ Added new algorithm case + return ( +
+ +
+ ); default: return (
@@ -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!

@@ -111,9 +118,9 @@ export default function DynamicProgrammingPage() { - {/* ✅ added */} - - + + + {/* ✅ Added */}