Skip to content

Commit ebd7f81

Browse files
committed
coin change visualizer added
1 parent 83fb13f commit ebd7f81

File tree

4 files changed

+345
-6
lines changed

4 files changed

+345
-6
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
export function coinChangeTabulation(coins = [], amount = 0) {
2+
// dp dimensions: (n + 1) x (amount + 1)
3+
const n = coins.length;
4+
const dp = Array.from({ length: n + 1 }, () =>
5+
new Array(amount + 1).fill(Infinity)
6+
);
7+
8+
const steps = [];
9+
10+
// Base case: dp[i][0] = 0 for all i
11+
for (let i = 0; i <= n; i++) {
12+
dp[i][0] = 0;
13+
steps.push({
14+
type: "base_case",
15+
row: i,
16+
col: 0,
17+
value: 0,
18+
dp: dp.map((r) => [...r]),
19+
message: `Base case: dp[${i}][0] = 0 (0 coins needed to make amount 0).`,
20+
});
21+
}
22+
23+
// Fill table row by row: i = 1..n (using first i coins), j = 1..amount
24+
for (let i = 1; i <= n; i++) {
25+
const coin = coins[i - 1];
26+
for (let j = 1; j <= amount; j++) {
27+
// Highlight cell selection
28+
steps.push({
29+
type: "select_cell",
30+
row: i,
31+
col: j,
32+
coin,
33+
dp: dp.map((r) => [...r]),
34+
message: `Computing dp[${i}][${j}] considering coin ${coin}.`,
35+
});
36+
37+
// Option 1: don't use current coin => dp[i-1][j]
38+
const option1 = dp[i - 1][j];
39+
steps.push({
40+
type: "option_without",
41+
row: i,
42+
col: j,
43+
coin,
44+
optionValue: option1,
45+
dp: dp.map((r) => [...r]),
46+
message: `Option 1 (without coin ${coin}): dp[${i - 1}][${j}] = ${option1 === Infinity ? "∞" : option1}.`,
47+
});
48+
49+
// Option 2: use current coin if j >= coin => dp[i][j-coin] + 1
50+
let option2 = Infinity;
51+
if (j - coin >= 0) {
52+
option2 = dp[i][j - coin] === Infinity ? Infinity : dp[i][j - coin] + 1;
53+
steps.push({
54+
type: "option_with",
55+
row: i,
56+
col: j,
57+
coin,
58+
optionValue:
59+
option2 === Infinity ? "∞" : option2,
60+
dp: dp.map((r) => [...r]),
61+
message: `Option 2 (use coin ${coin}): dp[${i}][${j - coin}] ${dp[i][j - coin] === Infinity ? "= ∞" : `= ${dp[i][j - coin]}`
62+
} → +1 => ${option2 === Infinity ? "∞" : option2}.`,
63+
});
64+
} else {
65+
steps.push({
66+
type: "option_with_unavailable",
67+
row: i,
68+
col: j,
69+
coin,
70+
optionValue: "N/A",
71+
dp: dp.map((r) => [...r]),
72+
message: `Option 2 (use coin ${coin}) not possible since ${coin} > ${j}.`,
73+
});
74+
}
75+
76+
// Take min and store
77+
const chosen = Math.min(option1, option2);
78+
dp[i][j] = chosen;
79+
80+
steps.push({
81+
type: "take_min",
82+
row: i,
83+
col: j,
84+
coin,
85+
chosen: chosen === Infinity ? "∞" : chosen,
86+
option1: option1 === Infinity ? "∞" : option1,
87+
option2: option2 === Infinity ? "∞" : option2,
88+
dp: dp.map((r) => [...r]),
89+
message: `dp[${i}][${j}] = min(${option1 === Infinity ? "∞" : option1}, ${option2 === Infinity ? "∞" : option2}) = ${chosen === Infinity ? "∞" : chosen}.`,
90+
});
91+
}
92+
}
93+
94+
const result = dp[n][amount] === Infinity ? -1 : dp[n][amount];
95+
96+
// Prepare visual steps (trim large dp values to Infinity string for clarity)
97+
const visualSteps = steps.map((step) => ({
98+
array: step.dp.map((r) => r.map((v) => (v === Infinity ? Infinity : v))),
99+
currentPosition: { row: step.row, col: step.col },
100+
coin: step.coin,
101+
message: step.message,
102+
type: step.type,
103+
extra: {
104+
option1: step.option1,
105+
option2: step.option2,
106+
chosen: step.chosen,
107+
optionValue: step.optionValue,
108+
},
109+
}));
110+
111+
return {
112+
steps: visualSteps,
113+
result,
114+
dpFinal: dp.map((r) => r.map((v) => (v === Infinity ? Infinity : v))),
115+
};
116+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import React, { useState, useEffect, useRef } from "react";
2+
3+
export default function CoinChangeVisualizer() {
4+
const [coins, setCoins] = useState([1, 2, 5]);
5+
const [amount, setAmount] = useState(11);
6+
const [speed, setSpeed] = useState(500);
7+
const [dp, setDp] = useState([]);
8+
const [steps, setSteps] = useState([]);
9+
const [currentStep, setCurrentStep] = useState(0);
10+
const [isRunning, setIsRunning] = useState(false);
11+
const intervalRef = useRef(null);
12+
13+
// --- Algorithm Logic ---
14+
const generateSteps = (coins, amount) => {
15+
const dpTable = Array(coins.length + 1)
16+
.fill(null)
17+
.map(() => Array(amount + 1).fill(Infinity));
18+
for (let i = 0; i <= coins.length; i++) dpTable[i][0] = 0;
19+
20+
const stepsList = [];
21+
for (let i = 1; i <= coins.length; i++) {
22+
for (let j = 1; j <= amount; j++) {
23+
const withoutCoin = dpTable[i - 1][j];
24+
const withCoin =
25+
j >= coins[i - 1] ? dpTable[i][j - coins[i - 1]] + 1 : Infinity;
26+
dpTable[i][j] = Math.min(withoutCoin, withCoin);
27+
28+
stepsList.push({
29+
i,
30+
j,
31+
coin: coins[i - 1],
32+
withoutCoin,
33+
withCoin,
34+
result: dpTable[i][j],
35+
dpSnapshot: dpTable.map((r) => [...r]),
36+
});
37+
}
38+
}
39+
return stepsList;
40+
};
41+
42+
// --- Visualization Control ---
43+
const startVisualization = () => {
44+
const newSteps = generateSteps(coins, amount);
45+
setSteps(newSteps);
46+
setDp(newSteps[0].dpSnapshot);
47+
setCurrentStep(0);
48+
setIsRunning(true);
49+
};
50+
51+
useEffect(() => {
52+
if (isRunning && steps.length > 0) {
53+
intervalRef.current = setInterval(() => {
54+
setCurrentStep((prev) => {
55+
if (prev >= steps.length - 1) {
56+
clearInterval(intervalRef.current);
57+
setIsRunning(false);
58+
return prev;
59+
}
60+
return prev + 1;
61+
});
62+
}, speed);
63+
}
64+
return () => clearInterval(intervalRef.current);
65+
}, [isRunning, steps, speed]);
66+
67+
useEffect(() => {
68+
if (steps[currentStep]) setDp(steps[currentStep].dpSnapshot);
69+
}, [currentStep, steps]);
70+
71+
const current = steps[currentStep];
72+
73+
// --- Color Logic ---
74+
const getCellColor = (i, j, cell) => {
75+
if (cell === 0) return "bg-green-600 text-white font-bold";
76+
if (current && current.i === i && current.j === j)
77+
return "bg-yellow-400 text-black animate-pulse shadow-lg shadow-yellow-400/40";
78+
if (i === coins.length && j === amount)
79+
return "bg-purple-600 text-white font-bold shadow-md";
80+
if (cell === Infinity) return "bg-gray-800 text-gray-400";
81+
return "bg-blue-600/70 text-white";
82+
};
83+
84+
return (
85+
<div className="p-4 md:p-8 bg-gray-950 min-h-screen text-white font-sans">
86+
<h1 className="text-3xl font-bold mb-8 text-center text-indigo-400">
87+
Coin Change Visualizer (Dynamic Programming)
88+
</h1>
89+
90+
{/* Input Controls */}
91+
<div className="flex flex-wrap justify-center gap-4 mb-8">
92+
<input
93+
type="text"
94+
value={coins.join(",")}
95+
onChange={(e) => setCoins(e.target.value.split(",").map(Number))}
96+
placeholder="Coins (e.g., 1,2,5)"
97+
className="p-2 rounded-md bg-gray-800 border border-gray-700 text-center w-48"
98+
/>
99+
<input
100+
type="number"
101+
value={amount}
102+
onChange={(e) => setAmount(Number(e.target.value))}
103+
placeholder="Amount"
104+
className="p-2 rounded-md bg-gray-800 border border-gray-700 text-center w-32"
105+
/>
106+
<input
107+
type="range"
108+
min="200"
109+
max="1500"
110+
value={speed}
111+
onChange={(e) => setSpeed(Number(e.target.value))}
112+
className="w-40 accent-indigo-500"
113+
/>
114+
<button
115+
onClick={startVisualization}
116+
className="px-4 py-2 bg-indigo-600 rounded-md hover:bg-indigo-500 transition"
117+
>
118+
Start
119+
</button>
120+
<button
121+
onClick={() => {
122+
setIsRunning(false);
123+
clearInterval(intervalRef.current);
124+
setSteps([]);
125+
setDp([]);
126+
}}
127+
className="px-4 py-2 bg-gray-700 rounded-md hover:bg-gray-600 transition"
128+
>
129+
Reset
130+
</button>
131+
</div>
132+
133+
{/* DP Table */}
134+
<div className="overflow-x-auto flex justify-center mb-10">
135+
<table className="border-separate border-spacing-1 text-center">
136+
<thead>
137+
<tr>
138+
<th className="p-2 bg-gray-800 border border-gray-700 rounded-md text-gray-300">
139+
Coins ↓ / Amount →
140+
</th>
141+
{Array.from({ length: amount + 1 }, (_, j) => (
142+
<th
143+
key={j}
144+
className="p-2 bg-gray-800 border border-gray-700 rounded-md text-sm"
145+
>
146+
{j}
147+
</th>
148+
))}
149+
</tr>
150+
</thead>
151+
<tbody>
152+
{dp.map((row, i) => (
153+
<tr key={i} className="even:bg-gray-900/40">
154+
<td className="px-3 py-2 bg-gray-800 border border-gray-700 rounded-md text-sm text-gray-300">
155+
{i === 0 ? "∅" : `[${coins[i - 1]}]`}
156+
</td>
157+
{row.map((cell, j) => (
158+
<td
159+
key={j}
160+
className={`w-12 h-12 border border-gray-700 rounded-md text-center transition-all duration-300 ${getCellColor(
161+
i,
162+
j,
163+
cell
164+
)}`}
165+
>
166+
{cell === Infinity ? "∞" : cell}
167+
</td>
168+
))}
169+
</tr>
170+
))}
171+
</tbody>
172+
</table>
173+
</div>
174+
175+
{/* Explanation Panel */}
176+
{current && (
177+
<div className="text-center bg-gray-900 border border-gray-700 rounded-xl p-6 shadow-lg max-w-2xl mx-auto">
178+
<p className="text-indigo-300 font-semibold mb-2">
179+
Step {currentStep + 1} / {steps.length}
180+
</p>
181+
<p className="text-gray-200">
182+
Computing{" "}
183+
<span className="text-yellow-400 font-medium">
184+
dp[{current.i}][{current.j}]
185+
</span>{" "}
186+
using coin{" "}
187+
<span className="text-cyan-400 font-medium">{current.coin}</span>
188+
</p>
189+
<p className="mt-3 text-gray-300">
190+
Without coin ={" "}
191+
<span className="text-red-400">
192+
{current.withoutCoin === Infinity ? "∞" : current.withoutCoin}
193+
</span>{" "}
194+
| With coin ={" "}
195+
<span className="text-green-400">
196+
{current.withCoin === Infinity ? "∞" : current.withCoin}
197+
</span>
198+
</p>
199+
<p className="mt-3 text-green-400 font-semibold">
200+
dp[{current.i}][{current.j}] = min(
201+
{current.withoutCoin === Infinity ? "∞" : current.withoutCoin},{" "}
202+
{current.withCoin === Infinity ? "∞" : current.withCoin}) ={" "}
203+
{current.result === Infinity ? "∞" : current.result}
204+
</p>
205+
</div>
206+
)}
207+
</div>
208+
);
209+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// src/pages/dynamic-programming/CoinChange.jsx
2+
import React from "react";
3+
import CoinChangeVisualizer from "../../components/dynamic-programming/CoinChangeVisualizer";
4+
5+
export default function CoinChange() {
6+
return <CoinChangeVisualizer />;
7+
}

src/pages/dynamic-programming/DyanmicProgrammingPage.jsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import Levenshtein from "./Levenshtein";
44
import MatrixChainMultiplication from "./MatrixChainMultiplication";
55
import FibonacciSequence from "./FibonacciSequence";
66
import Knapsack from "./Knapsack";
7-
import PascalTriangle from "./PascalTriangle";
7+
import PascalTriangle from "./PascalTriangle";
88
import LCSPage from "./LCS";
9+
import CoinChange from "./CoinChange"; // ✅ Added import
910

1011
export default function DynamicProgrammingPage() {
1112
const [selectedAlgo, setSelectedAlgo] = useState("");
@@ -37,7 +38,7 @@ export default function DynamicProgrammingPage() {
3738
<Knapsack />
3839
</div>
3940
);
40-
case "PascalTriangle":
41+
case "PascalTriangle":
4142
return (
4243
<div className="md:w-full w-screen overflow-clip p-2">
4344
<PascalTriangle />
@@ -49,6 +50,12 @@ export default function DynamicProgrammingPage() {
4950
<LCSPage />
5051
</div>
5152
);
53+
case "CoinChange": // ✅ Added new algorithm case
54+
return (
55+
<div className="md:w-full w-screen overflow-clip p-2">
56+
<CoinChange />
57+
</div>
58+
);
5259
default:
5360
return (
5461
<div className="flex flex-col items-center justify-center text-center p-6 min-h-screen bg-gray-950">
@@ -65,7 +72,7 @@ export default function DynamicProgrammingPage() {
6572
Understand how overlapping subproblems and optimal
6673
substructures work in real time. Visualize table-filling,
6774
recursion trees, and memoization flow with stunning
68-
animations! ⚙️🧩
75+
animations!
6976
</p>
7077
</div>
7178
<div className="flex items-center justify-center gap-6">
@@ -111,9 +118,9 @@ export default function DynamicProgrammingPage() {
111118
<option value="MatrixChainMultiplication">Matrix Chain Multiplication</option>
112119
<option value="Fibonacci">Fibonacci Sequence</option>
113120
<option value="Knapsack">Knapsack</option>
114-
<option value="PascalTriangle">Pascal Triangle</option> {/* ✅ added */}
115-
<option value="Knapsack">Knapsack</option>
116-
<option value="LongestCommonSubsequence">Longest Common Subsequence</option>
121+
<option value="PascalTriangle">Pascal Triangle</option>
122+
<option value="LongestCommonSubsequence">Longest Common Subsequence</option>
123+
<option value="CoinChange">Coin Change</option> {/* ✅ Added */}
117124
</select>
118125

119126
<button

0 commit comments

Comments
 (0)