diff --git a/README.md b/README.md index d57c2fa..9748f7b 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,31 @@ Understand searching logic through dynamic comparisons. --- +#### 🔹 Sliding Window Algorithms +Demonstrate how the sliding window technique optimizes time complexity in problems involving subarrays and substrings. + +**Algorithms Included:** +- Maximum Sum of Subarray of Size K + +**Interactive Options:** +- Adjust window size +- Control animation speed +- Real-time window movement visualization +- Dynamic highlighting of elements within the window +- Step-by-step explanation of window expansion and contraction + +**Algorithm Overview:** +The Sliding Window technique maintains a subset of elements using two pointers (start, end) that "slide" over the array or string to efficiently compute results without redundant recalculations. + +**General Approach:** +1. Initialize start and end pointers +2. Expand the window by moving end +3. Process or evaluate current window state +4. Shrink the window from start when constraints are violated +5. Update the result as needed during traversal + +--- + #### 🔹 Pathfinding Algorithms (Graph / 2D Grid) Visualize how algorithms explore and find paths across a grid. @@ -186,12 +211,19 @@ Algo-Visualizer/ │ ├── algorithms/ │ │ ├── sorting/ │ │ ├── searching/ +│ │ ├── sliding-window/ │ │ ├── pathfinding/ │ │ ├── graph/ │ ├── components/ +│ │ ├── sorting/ +│ │ ├── searching/ +│ │ ├── sliding-window/ +│ │ ├── pathfinding/ +│ │ ├── graph/ │ ├── pages/ │ │ ├── sorting/ │ │ ├── searching/ +│ │ ├── sliding-window/ │ │ ├── pathfinding/ │ │ ├── graph/ │ ├── utils/ diff --git a/public/Sliding-Window.png b/public/Sliding-Window.png new file mode 100644 index 0000000..037c7a2 Binary files /dev/null and b/public/Sliding-Window.png differ diff --git a/src/App.jsx b/src/App.jsx index 71a3bf5..68f06cb 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,6 +8,7 @@ import DSPage from "./pages/dataStructure/datastructurePage.jsx" import DynamicProgrammingPage from "./pages/dynamic-programming/DyanmicProgrammingPage.jsx"; import Searchingpage from "./pages/searching/searchingPage"; import RecursionPage from "./pages/Recursion/RecursionPage"; +import SlidingWindowPage from "./pages/sliding-window/SlidingWindowPage"; function App() { return ( @@ -20,6 +21,7 @@ function App() { } /> } /> }/> + }/> ); diff --git a/src/algorithms/sliding-window/maxSumSubarray.js b/src/algorithms/sliding-window/maxSumSubarray.js new file mode 100644 index 0000000..4400e5e --- /dev/null +++ b/src/algorithms/sliding-window/maxSumSubarray.js @@ -0,0 +1,88 @@ +export function* maxSumSubarray(array, k) { + if (array.length === 0) { + yield { + type: "error", + message: "Array cannot be empty", + windowStart: -1, windowEnd: -1, currentSum: 0, maxSum: 0 + }; + return; + } + if (k > array.length) { + yield { + type: "error", + message: `Window size ${k} cannot be greater than array length ${array.length}`, + windowStart: -1, windowEnd: -1, currentSum: 0, maxSum: 0 + }; + return; + } + if (k <= 0) { + yield { + type: "error", + message: "Window size must be greater than 0", + windowStart: -1, windowEnd: -1, currentSum: 0, maxSum: 0 + }; + return; + } + let windowStart = 0; + let windowEnd = 0; + let currentSum = 0; + let maxSum = Number.NEGATIVE_INFINITY; + yield { + type: "initialize", + message: `Initializing: Calculating sum of first window [0, ${k - 1}]`, + windowStart: 0,windowEnd: k - 1,currentSum: 0,maxSum: 0 + }; + for (let i = 0; i < k; i++) { + currentSum += array[i]; + yield { + type: "expand", + message: `Adding element at index ${i}: ${array[i]}. Current sum: ${currentSum}`, + windowStart: 0,windowEnd: i,currentSum: currentSum,maxSum: maxSum + }; + } + maxSum = currentSum; + windowEnd = k - 1; + + yield { + type: "window_ready", + message: `First window sum: ${currentSum}. This is our initial maximum.`, + windowStart: 0, windowEnd: k - 1, currentSum: currentSum, maxSum: maxSum + }; + for (let i = k; i < array.length; i++) { + const removedElement = array[i - k]; + const addedElement = array[i]; + yield { + type: "slide_start", + message: `Sliding window: Removing element at index ${i - k} (${removedElement}), adding element at index ${i} (${addedElement})`, + windowStart: i - k, windowEnd: i - 1, currentSum: currentSum, maxSum: maxSum, removedIndex: i - k, addedIndex: i + }; + currentSum = currentSum - removedElement + addedElement; + windowStart = i - k + 1; + windowEnd = i; + yield { + type: "slide_update", + message: `Window moved: New sum = ${currentSum} (previous: ${currentSum + removedElement - addedElement}, removed: ${removedElement}, added: ${addedElement})`, + windowStart: windowStart, windowEnd: windowEnd, currentSum: currentSum, maxSum: maxSum, removedIndex: i - k, addedIndex: i + }; + if (currentSum > maxSum) { + maxSum = currentSum; + yield { + type: "new_max", + message: `New maximum found! Sum: ${maxSum} at window [${windowStart}, ${windowEnd}]`, + windowStart: windowStart, windowEnd: windowEnd, currentSum: currentSum, maxSum: maxSum + }; + } else { + yield { + type: "window_ready", + message: `Current sum: ${currentSum} (not greater than max: ${maxSum})`, + windowStart: windowStart, windowEnd: windowEnd, currentSum: currentSum, maxSum: maxSum + }; + } + } + yield { + type: "done", + message: `Algorithm completed! Maximum sum of subarray of size ${k} is ${maxSum}`, + windowStart: windowStart, windowEnd: windowEnd, currentSum: currentSum, maxSum: maxSum + }; +} + diff --git a/src/components/sliding-window/MaxSumSubarrayVisualizer.jsx b/src/components/sliding-window/MaxSumSubarrayVisualizer.jsx new file mode 100644 index 0000000..f12146d --- /dev/null +++ b/src/components/sliding-window/MaxSumSubarrayVisualizer.jsx @@ -0,0 +1,69 @@ +import React from "react"; + +export default function MaxSumSubarrayVisualizer({array, windowStart, windowEnd, currentSum, maxSum, type, removedIndex, addedIndex}) { + if (!array || array.length === 0) { + return ( +
+ No array data to visualize +
+ ); + } + const maxValue = Math.max(...array, 1); + const containerHeight = 320; + + const getColor = (idx) => { + if (windowStart < 0 || windowEnd < 0) return "bg-gray-600"; + if (type === "slide_start" && idx === removedIndex) return "bg-red-500"; + if ((type === "slide_start" || type === "slide_update") && idx === addedIndex) + return "bg-yellow-400"; + if (type === "new_max" && idx >= windowStart && idx <= windowEnd) + return "bg-purple-500 animate-pulse"; + if (idx === windowStart || idx === windowEnd) return "bg-green-500"; + if (idx >= windowStart && idx <= windowEnd) return "bg-blue-500"; + return "bg-gray-500"; + }; + + return ( +
+
+ {array.map((value, idx) => { + const color = getColor(idx); + const height = Math.max((value / maxValue) * containerHeight, 40); + return ( +
+
+ + {value} + +
+ ); + })} +
+ {windowStart >= 0 && windowEnd >= 0 && ( +
+
+ Window:{" "} + + [{windowStart}, {windowEnd}] + +
+
+ Sum:{" "} + + {currentSum} + +
+
+ Max:{" "} + + {maxSum !== Number.NEGATIVE_INFINITY ? maxSum : "N/A"} + +
+
+ )} +
+ ); +} diff --git a/src/pages/Homepage.jsx b/src/pages/Homepage.jsx index c9df667..318180d 100644 --- a/src/pages/Homepage.jsx +++ b/src/pages/Homepage.jsx @@ -23,6 +23,15 @@ const sections = [ link: "/searching", flag: false }, + { + title: "Sliding Window Algorithms", + description: + "Visualize how the sliding window technique efficiently processes subarrays and substrings with dynamic window movement.", + phase: "Phase 1 (MVP)", + img: "/Sliding-Window.png", + link: "/sliding-window", + flag: false + }, { title: "Pathfinding Algorithms", description: diff --git a/src/pages/sliding-window/MaxSumSubarray.jsx b/src/pages/sliding-window/MaxSumSubarray.jsx new file mode 100644 index 0000000..2b92d82 --- /dev/null +++ b/src/pages/sliding-window/MaxSumSubarray.jsx @@ -0,0 +1,218 @@ +import React, { useState, useEffect, useRef } from "react"; +import { Toaster, toast } from "react-hot-toast"; +import MaxSumSubarrayVisualizer from "../../components/sliding-window/MaxSumSubarrayVisualizer"; +import { maxSumSubarray } from "../../algorithms/sliding-window/maxSumSubarray"; + +export default function MaxSumSubarray() { + const [array, setArray] = useState([2, 1, 5, 1, 3, 2]); + const [input, setInput] = useState("2,1,5,1,3,2"); + const [windowSize, setWindowSize] = useState(3); + const [speed, setSpeed] = useState(800); + const [isRunning, setIsRunning] = useState(false); + const [currentStep, setCurrentStep] = useState(null); + const [steps, setSteps] = useState([]); + const [currentStepIndex, setCurrentStepIndex] = useState(0); + const intervalRef = useRef(null); + const hasNotifiedRef = useRef(false); + + // Handle input + const handleInputChange = (e) => { + const value = e.target.value; + setInput(value); + const numbers = value + .split(",") + .map((n) => parseInt(n.trim())) + .filter((n) => !isNaN(n)); + setArray(numbers); + resetSteps(); + }; + const handleWindowSizeChange = (e) => { + const size = parseInt(e.target.value); + if (!isNaN(size) && size > 0) { + setWindowSize(size); + resetSteps(); + } + }; + const resetSteps = () => { + setCurrentStep(null); + setSteps([]); + setCurrentStepIndex(0); + hasNotifiedRef.current = false; + }; + const startVisualization = async () => { + if (isRunning || array.length === 0) { + toast.error("Please ensure the array is valid."); + return; + } + if (windowSize > array.length) { + toast.error(`Window size cannot exceed array length (${array.length})`); + return; + } + if (windowSize <= 0) { + toast.error("Window size must be greater than 0"); + return; + } + setIsRunning(true); + resetSteps(); + + const gen = maxSumSubarray(array, windowSize); + const allSteps = []; + try { + for (let step of gen) allSteps.push(step); + setSteps(allSteps); + setCurrentStep(allSteps[0]); + }catch (error) { + toast.error(error.message); + setIsRunning(false); + } + }; + useEffect(() => { + if (!isRunning || steps.length === 0) return; + + clearInterval(intervalRef.current); + let index = 0; + intervalRef.current = setInterval(() => { + setCurrentStepIndex((prev) => { + index = prev + 1; + + if (index >= steps.length) { + clearInterval(intervalRef.current); + setIsRunning(false); + + if (!hasNotifiedRef.current && steps.length > 0) { + const finalStep = steps[steps.length - 1]; + toast.success( + `✅ Maximum sum found: ${finalStep.maxSum} (Window: [${finalStep.windowStart}, ${finalStep.windowEnd}])`, + { duration: 3000 } + ); + hasNotifiedRef.current = true; + } + return prev; + } + const step = steps[index]; + setCurrentStep(step); + if (step.type === "new_max") { + toast.success(` Yo! New maximum found: ${step.maxSum}`, { + duration: 2000, + }); + } + return index; + }); + }, speed); + return () => clearInterval(intervalRef.current); + }, [isRunning, steps, speed]); + + const handleReset = () => { + setIsRunning(false); + clearInterval(intervalRef.current); + setArray([2, 1, 5, 1, 3, 2]); + setInput("2,1,5,1,3,2"); + setWindowSize(3); + setSpeed(800); + resetSteps(); + }; + + return ( +
+ +

+ Sliding Window — Max Sum Subarray +

+ {/* Controls */} +
+ + +
+ + setSpeed(Number(e.target.value))} + disabled={isRunning} + className="w-40 accent-indigo-500" /> +
+
+ + +
+
+
+ +
+ {currentStep && ( +
+

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

+ {currentStep.message && ( +

{currentStep.message}

+ )} +
+ + Window:{" "} + + [{currentStep.windowStart}, {currentStep.windowEnd}] + + + + Current Sum:{" "} + + {currentStep.currentSum} + + + + Max Sum:{" "} + + {currentStep.maxSum} + + +
+
+ )} +
+ ); +} +function Control({ label, type = "text", value, onChange, placeholder, disabled }) { + return ( +
+ + +
+ ); +} diff --git a/src/pages/sliding-window/SlidingWindowPage.jsx b/src/pages/sliding-window/SlidingWindowPage.jsx new file mode 100644 index 0000000..60a8ae6 --- /dev/null +++ b/src/pages/sliding-window/SlidingWindowPage.jsx @@ -0,0 +1,63 @@ +import React, { useState } from "react"; +import MaxSumSubarray from "./MaxSumSubarray"; + +export default function SlidingWindowPage() { + const [selectedAlgo, setSelectedAlgo] = useState(""); + const renderAlgorithm = () => { + switch (selectedAlgo) { + case "maxSumSubarray": + return ; + default: + return ( +
+
+
+
+

+ Sliding Window Visualizer +

+

+ Understand how the sliding window technique optimizes time complexity + in problems involving subarrays and substrings. Watch the window + dynamically move and update across the input sequence! +

+
+
+
+
+ ); + } + }; + + return ( +
+ {/* Sidebar */} +
+

+ Sliding Window Panel +

+ + + + + + + + ← Back to Home + +
+
+
+ {renderAlgorithm()} +
+
+
+ ); +} +