From 342f9ae1bbe1773209511b3111190600ca8cd86d Mon Sep 17 00:00:00 2001 From: indar suthar Date: Wed, 12 Nov 2025 03:45:21 +0530 Subject: [PATCH 1/2] feat: maxsumsubarray algo with sliding-window --- README.md | 32 +++ src/App.jsx | 2 + .../sliding-window/maxSumSubarray.js | 88 +++++++ .../MaxSumSubarrayVisualizer.jsx | 69 ++++++ src/pages/Homepage.jsx | 9 + src/pages/sliding-window/MaxSumSubarray.jsx | 218 ++++++++++++++++++ .../sliding-window/SlidingWindowPage.jsx | 63 +++++ 7 files changed, 481 insertions(+) create mode 100644 src/algorithms/sliding-window/maxSumSubarray.js create mode 100644 src/components/sliding-window/MaxSumSubarrayVisualizer.jsx create mode 100644 src/pages/sliding-window/MaxSumSubarray.jsx create mode 100644 src/pages/sliding-window/SlidingWindowPage.jsx 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/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..b5ed3fc 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: "https://media.geeksforgeeks.org/wp-content/uploads/20230626162234/Sliding-Window-Technique.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()} +
+
+
+ ); +} + From 9774b7df0f84cdfc9a0c0b8b8033383053601915 Mon Sep 17 00:00:00 2001 From: indar suthar Date: Wed, 12 Nov 2025 03:49:07 +0530 Subject: [PATCH 2/2] feat: adding image for sliding-window --- public/Sliding-Window.png | Bin 0 -> 20270 bytes src/pages/Homepage.jsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 public/Sliding-Window.png diff --git a/public/Sliding-Window.png b/public/Sliding-Window.png new file mode 100644 index 0000000000000000000000000000000000000000..037c7a2ad8e440f4d4a49e39086a3d4f4a2d3936 GIT binary patch literal 20270 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