diff --git a/src/algorithms/Recursion/SubsetSum.js b/src/algorithms/Recursion/SubsetSum.js new file mode 100644 index 0000000..a235f79 --- /dev/null +++ b/src/algorithms/Recursion/SubsetSum.js @@ -0,0 +1,118 @@ +export function getSubsetSumSteps(arr = [], target = 0) { + const n = arr.length; + const steps = []; + + const dp = Array.from({ length: n + 1 }, () => + Array(target + 1).fill(false) + ); + + dp[0][0] = true; + + steps.push({ + type: "init", + message: "Initialize DP table: dp[0][0] = true (empty subset → sum 0)", + dp: structuredClone(dp), + }); + + for (let i = 1; i <= n; i++) { + const val = arr[i - 1]; + + steps.push({ + type: "init_row", + row: i, + value: val, + message: `Processing element arr[${i - 1}] = ${val}`, + dp: structuredClone(dp), + }); + + for (let sum = 0; sum <= target; sum++) { + steps.push({ + type: "process_cell", + i, + sum, + value: val, + message: `Checking if we can form sum ${sum} using first ${i} elements`, + dp: structuredClone(dp), + }); + + if (dp[i - 1][sum]) { + dp[i][sum] = true; + steps.push({ + type: "true_set", + i, + sum, + by: "exclude", + message: `dp[${i}][${sum}] = true (exclude ${val})`, + dp: structuredClone(dp), + }); + } + + if (sum >= val && dp[i - 1][sum - val]) { + dp[i][sum] = true; + steps.push({ + type: "true_set", + i, + sum, + by: "include", + message: `dp[${i}][${sum}] = true (include ${val})`, + dp: structuredClone(dp), + }); + } + } + + steps.push({ + type: "complete_row", + row: i, + message: `Completed row ${i} (processed arr[${i - 1}] = ${val})`, + dp: structuredClone(dp), + }); + } + + const isPossible = dp[n][target]; + + if (!isPossible) { + steps.push({ + type: "no_solution", + message: `No subset found that adds up to target ${target}.`, + dp: structuredClone(dp), + }); + return { steps, solutions: [], solutionCount: 0 }; + } + + const solution = []; + let i = n, sum = target; + + while (i > 0 && sum >= 0) { + if (dp[i - 1][sum]) { + steps.push({ + type: "decision", + message: `arr[${i - 1}] = ${arr[i - 1]} was EXCLUDED`, + dp: structuredClone(dp), + }); + i--; + } else { + const val = arr[i - 1]; + solution.push(val); + steps.push({ + type: "decision", + message: `arr[${i - 1}] = ${val} was INCLUDED`, + dp: structuredClone(dp), + }); + sum -= val; + i--; + } + } + + steps.push({ + type: "solution", + solution: solution.reverse(), + message: `✅ Found solution subset: [${solution.join(", ")}]`, + dp: structuredClone(dp), + }); + + return { + steps, + solutions: [solution], + solutionCount: 1, + }; +} diff --git a/src/components/Recursion/SubsetSumVisualizer.jsx b/src/components/Recursion/SubsetSumVisualizer.jsx new file mode 100644 index 0000000..b197f17 --- /dev/null +++ b/src/components/Recursion/SubsetSumVisualizer.jsx @@ -0,0 +1,263 @@ +import React, { useEffect, useRef, useState } from "react"; +import { getSubsetSumSteps } from "../../algorithms/Recursion/SubsetSum"; + +const MIN_SPEED = 50; +const MAX_SPEED = 2000; + +const Color = ({ color, text }) => ( +
+
+ {text} +
+); + +export default function SubsetSumVisualizer() { + const [inputArray, setInputArray] = useState("3, 34, 4, 12, 5, 2"); + const [arr, setArr] = useState([3, 34, 4, 12, 5, 2]); + const [target, setTarget] = useState(9); + + const [steps, setSteps] = useState([]); + const [idx, setIdx] = useState(0); + const [playing, setPlaying] = useState(false); + const [solutions, setSolutions] = useState([]); + + const [speed, setSpeed] = useState(400); + const timerRef = useRef(null); + + const parseArray = () => { + const parsed = inputArray + .split(",") + .map((s) => Number(s.trim())) + .filter((n) => !isNaN(n)); + + return parsed.length ? parsed : null; + }; + + const regenerate = () => { + const res = getSubsetSumSteps(arr, target); + setSteps(res.steps); + setSolutions(res.solutions); + setIdx(0); + setPlaying(false); + }; + + const handleStart = () => { + const parsed = parseArray(); + if (!parsed) { + alert("Invalid array"); + return; + } + + clearTimeout(timerRef.current); + setPlaying(false); + + setArr(parsed); + + setTimeout(() => { + regenerate(); + setIdx(0); + setPlaying(true); + }, 80); + }; + const handlePauseResume = () => { + if (steps.length === 0) return; + clearTimeout(timerRef.current); + setPlaying((p) => !p); + }; + const handleReset = () => { + clearTimeout(timerRef.current); + setPlaying(false); + setIdx(0); + }; + useEffect(() => { + clearTimeout(timerRef.current); + + if (playing && idx < steps.length - 1) { + timerRef.current = setTimeout(() => setIdx((i) => i + 1), speed); + } + + return () => clearTimeout(timerRef.current); + }, [playing, idx, steps, speed]); + + const step = steps[idx] || {}; + + const getCellColor = (i, s) => { + if (!step.dp) return "bg-gray-700"; + + if (step.type === "process_cell" && step.i === i && step.sum === s) + return "bg-blue-400 text-black"; + + if (step.type === "true_set" && step.i === i && step.sum === s) + return step.by === "include" + ? "bg-green-500 text-black" + : "bg-green-100 text-black"; + + return step.dp[i][s] ? "bg-green-700" : "bg-gray-800"; + }; + + return ( +
+
+

+ Subset Sum Visualizer +

+ {/*Controls*/} +
+
+
+ + setInputArray(e.target.value)} + className="w-full mt-1 p-2 rounded bg-gray-700 border border-gray-600" + placeholder="comma-separated" + /> +
+
+ + setTarget(Number(e.target.value))} + className="w-20 mt-1 p-2 rounded bg-gray-700 border border-gray-600" + /> +
+ +
+ + setSpeed(Number(e.target.value))} + className="w-24 mt-1 p-2 rounded bg-gray-700 border border-gray-600" + /> +
+ + + + + + +
+
+ +
+ {/* array */} +
+

+ Array Elements : (Each Row in DP corresponds to one element) +

+ +
+ {arr.map((v, i) => ( +
+ {v} +
+ ))} +
+
+ + {/*dp table*/} +
+

+ DP Table (dp[i][sum]) +

+ + {step.dp && ( +
+ + + + + {step.dp[0].map((_, s) => ( + + ))} + + + + + {step.dp.map((row, i) => ( + + + {row.map((_, s) => ( + + ))} + + ))} + +
i / sum + {s} +
i={i} + {step.dp[i][s] ? "T" : "F"} +
+
+ )} + +
+

Explanation

+

+ {step.message || "Press Start to begin."} +

+
+
+ +
+

Solutions

+ + {solutions.length > 0 ? ( +
+ {solutions.map((s, i) => ( +
+ # Subset: [{s.join(", ")}] +
+ ))} +
+ ) : ( +
No solutions found yet.
+ )} + +

Color

+
+ + + +
+
+
+
+
+ ); +} diff --git a/src/pages/Recursion/RecursionPage.jsx b/src/pages/Recursion/RecursionPage.jsx index 525f72f..e48c385 100644 --- a/src/pages/Recursion/RecursionPage.jsx +++ b/src/pages/Recursion/RecursionPage.jsx @@ -4,6 +4,7 @@ import MazeSolver from "./MazeSolver"; import NQueens from "./NQueens"; import Sudoku from "./sudokuSolver"; import TowerOfHanoi from "./towerOfHanoi"; +import SubsetSum from "./SubsetSum"; export default function RecursionPage() { const [selectedAlgo, setSelectedAlgo] = useState(""); const [sidebarOpen, setSidebarOpen] = useState(true); @@ -34,6 +35,12 @@ export default function RecursionPage() {
); + case "SubsetSum": + return ( +
+ +
+ ); default: return ( @@ -79,6 +86,7 @@ export default function RecursionPage() { +