diff --git a/src/algorithms/dynamic-programming/kmpSearchAlgo.js b/src/algorithms/dynamic-programming/kmpSearchAlgo.js
new file mode 100644
index 0000000..136dcf4
--- /dev/null
+++ b/src/algorithms/dynamic-programming/kmpSearchAlgo.js
@@ -0,0 +1,161 @@
+export function computeLPSSteps(pattern) {
+ const m = pattern.length;
+ const lps = Array(m).fill(0);
+ const steps = [];
+ let len = 0;
+ let i = 1;
+
+ steps.push({
+ lps: [...lps],
+ i: 1,
+ len: 0,
+ message: "Initializing LPS table. lps[0] is always 0. Starting from i = 1.",
+ highlight: { i: 1, len: 0, lps: -1 },
+ });
+
+ while (i < m) {
+ steps.push({
+ lps: [...lps],
+ i: i,
+ len: len,
+ message: `Comparing pattern[${i}] ('${pattern[i]}') and pattern[${len}] ('${pattern[len]}').`,
+ highlight: { i: i, len: len, lps: -1 },
+ });
+
+ if (pattern[i] === pattern[len]) {
+ len++;
+ lps[i] = len;
+ steps.push({
+ lps: [...lps],
+ i: i,
+ len: len,
+ message: `Match! lps[${i}] = ${len}. Increment i and len.`,
+ highlight: { i: i, len: len, lps: i, state: 'match' },
+ });
+ i++;
+ } else {
+ if (len !== 0) {
+ len = lps[len - 1];
+ steps.push({
+ lps: [...lps],
+ i: i,
+ len: len,
+ message: `Mismatch. Falling back: len = lps[${len - 1}] = ${len}.`,
+ highlight: { i: i, len: len, lps: -1, state: 'mismatch' },
+ });
+ } else {
+ lps[i] = 0;
+ steps.push({
+ lps: [...lps],
+ i: i,
+ len: len,
+ message: `Mismatch. No fallback. lps[${i}] = 0. Increment i.`,
+ highlight: { i: i, len: len, lps: i, state: 'mismatch' },
+ });
+ i++;
+ }
+ }
+ }
+
+ steps.push({
+ lps: [...lps],
+ i: m,
+ len: len,
+ message: "LPS table computation complete.",
+ highlight: { i: m, len: len, lps: -1, state: 'done' },
+ });
+ return { steps, lpsTable: lps };
+ }
+
+ export function kmpSearchSteps(text, pattern, lps) {
+ const n = text.length;
+ const m = pattern.length;
+ const steps = [];
+ const matches = [];
+ let i = 0;
+ let j = 0;
+
+ steps.push({
+ i: i,
+ j: j,
+ matches: [...matches],
+ message: "Starting KMP search. i = 0 (text), j = 0 (pattern).",
+ highlight: { i: 0, j: 0, state: 'idle' }
+ });
+
+ while (i < n) {
+ if (j === 0) {
+ steps.push({
+ i: i,
+ j: j,
+ matches: [...matches],
+ message: `Comparing text[${i}] ('${text[i]}') and pattern[${j}] ('${pattern[j]}').`,
+ highlight: { i: i, j: j, state: 'compare' }
+ });
+ } else {
+ steps.push({
+ i: i,
+ j: j,
+ matches: [...matches],
+ message: `Comparing text[${i}] ('${text[i]}') and pattern[${j}] ('${pattern[j]}').`,
+ highlight: { i: i, j: j, state: 'compare' }
+ });
+ }
+
+
+ if (pattern[j] === text[i]) {
+ i++;
+ j++;
+ }
+
+ if (j === m) {
+ const matchIndex = i - j;
+ matches.push(matchIndex);
+ steps.push({
+ i: i,
+ j: j,
+ matches: [...matches],
+ message: `PATTERN FOUND! at index ${matchIndex}.`,
+ highlight: { i: i, j: j, state: 'match', matchIndex: matchIndex }
+ });
+ j = lps[j - 1];
+ steps.push({
+ i: i,
+ j: j,
+ matches: [...matches],
+ message: `Jumping pattern pointer using LPS: j = lps[${m - 1}] = ${j}.`,
+ highlight: { i: i, j: j, state: 'jump' }
+ });
+ } else if (i < n && pattern[j] !== text[i]) {
+ if (j !== 0) {
+ j = lps[j - 1];
+ steps.push({
+ i: i,
+ j: j,
+ matches: [...matches],
+ message: `Mismatch. Jumping pattern pointer using LPS: j = lps[${j - 1}] = ${j}.`,
+ highlight: { i: i, j: j, state: 'jump' }
+ });
+ } else {
+ i++;
+ steps.push({
+ i: i,
+ j: j,
+ matches: [...matches],
+ message: `Mismatch at j=0. Incrementing text pointer i to ${i}.`,
+ highlight: { i: i, j: j, state: 'mismatch' }
+ });
+ }
+ }
+ }
+
+ steps.push({
+ i: n,
+ j: j,
+ matches: [...matches],
+ message: `Search complete. ${matches.length} match(es) found.`,
+ highlight: { i: n, j: j, state: 'done' }
+ });
+
+ return { steps, matches };
+ }
\ No newline at end of file
diff --git a/src/components/dynamic-programming/kmpSearchAlgovisual.jsx b/src/components/dynamic-programming/kmpSearchAlgovisual.jsx
new file mode 100644
index 0000000..fcdb7d6
--- /dev/null
+++ b/src/components/dynamic-programming/kmpSearchAlgovisual.jsx
@@ -0,0 +1,389 @@
+import React, { useState, useMemo, useEffect, useRef } from "react";
+import { computeLPSSteps, kmpSearchSteps } from "@/algorithms/string/kmpAlgo";
+
+const LPSDisplay = ({ pattern, lpsState }) => {
+ const { lps, highlight, message } = lpsState;
+ const { i, len, state } = highlight;
+
+ return (
+
+
Phase 1: Building LPS Table
+
+
+
Pattern:
+ {pattern.split("").map((char, index) => (
+
+ {char}
+
+ ))}
+
+
+
+ {pattern.split("").map((_, index) => (
+
+ {index === i && i }
+ {index === len && len }
+
+ ))}
+
+
+
+
LPS:
+ {lps.map((value, index) => (
+
+ {value}
+
+ ))}
+
+
+
+
Current Action
+
{message}
+
+
+ );
+};
+
+const SearchDisplay = ({ text, pattern, searchState }) => {
+ const { i, j, matches, highlight, message } = searchState;
+ const { state, matchIndex } = highlight;
+ const m = pattern.length;
+ const patternOffset = i - j;
+
+ return (
+
+
Phase 2: Searching Text
+
+
+
Text:
+ {text.split("").map((char, index) => (
+
+ {char}
+
+ ))}
+
+
+
+ {text.split("").map((_, index) => (
+
+ {index === i && i }
+
+ ))}
+
+
+
+
Pattern:
+ {pattern.split("").map((char, index) => (
+
+ {char}
+
+ ))}
+
+
+
+ {pattern.split("").map((_, index) => (
+
+ {index === j && j }
+
+ ))}
+
+
+
+
Current Action
+
{message}
+
+
+ );
+};
+
+const SPEED_OPTIONS = {
+ "Slow": 1500,
+ "Medium": 500,
+ "Fast": 200,
+};
+
+export default function KMPVisualizer() {
+ const [text, setText] = useState("ABABDABACDABABCABAB");
+ const [pattern, setPattern] = useState("ABABCABAB");
+
+ const [lpsSteps, setLpsSteps] = useState([]);
+ const [searchSteps, setSearchSteps] = useState([]);
+ const [lpsTable, setLpsTable] = useState([]);
+ const [matches, setMatches] = useState([]);
+
+ const [currentStep, setCurrentStep] = useState(0);
+ const [visualizationPhase, setVisualizationPhase] = useState("idle");
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [speed, setSpeed] = useState(SPEED_OPTIONS["Medium"]);
+ const timerRef = useRef(null);
+
+ const handleCompute = () => {
+ setIsPlaying(false);
+ clearInterval(timerRef.current);
+
+ if (!pattern) {
+ alert("Please enter a pattern.");
+ return;
+ }
+
+ const { steps: newLpsSteps, lpsTable: newLpsTable } = computeLPSSteps(pattern);
+ const { steps: newSearchSteps, matches: newMatches } = kmpSearchSteps(text, pattern, newLpsTable);
+
+ setLpsSteps(newLpsSteps);
+ setLpsTable(newLpsTable);
+ setSearchSteps(newSearchSteps);
+ setMatches(newMatches);
+
+ setCurrentStep(0);
+ setVisualizationPhase("lps");
+ };
+
+ useEffect(() => {
+ handleCompute();
+ }, []);
+
+ useEffect(() => {
+ if (isPlaying) {
+ timerRef.current = setInterval(() => {
+ handleNext(true);
+ }, speed);
+ } else {
+ clearInterval(timerRef.current);
+ }
+ return () => clearInterval(timerRef.current);
+ }, [isPlaying, speed, currentStep, visualizationPhase]);
+
+ const togglePlay = () => {
+ if (visualizationPhase === 'done') {
+ setCurrentStep(0);
+ setVisualizationPhase('lps');
+ setIsPlaying(true);
+ } else {
+ setIsPlaying(!isPlaying);
+ }
+ };
+
+ const handleNext = (isAutoPlay = false) => {
+ if (!isAutoPlay) setIsPlaying(false);
+
+ if (visualizationPhase === 'lps') {
+ if (currentStep < lpsSteps.length - 1) {
+ setCurrentStep(currentStep + 1);
+ } else {
+ setCurrentStep(0);
+ setVisualizationPhase('search');
+ }
+ } else if (visualizationPhase === 'search') {
+ if (currentStep < searchSteps.length - 1) {
+ setCurrentStep(currentStep + 1);
+ } else {
+ setVisualizationPhase('done');
+ setIsPlaying(false);
+ }
+ }
+ };
+
+ const handlePrev = () => {
+ setIsPlaying(false);
+
+ if (visualizationPhase === 'search') {
+ if (currentStep > 0) {
+ setCurrentStep(currentStep - 1);
+ } else {
+ setCurrentStep(lpsSteps.length - 1);
+ setVisualizationPhase('lps');
+ }
+ } else if (visualizationPhase === 'lps') {
+ if (currentStep > 0) {
+ setCurrentStep(currentStep - 1);
+ }
+ }
+ };
+
+ const handleTextChange = (e) => {
+ setText(e.target.value);
+ setVisualizationPhase('idle');
+ }
+
+ const handlePatternChange = (e) => {
+ setPattern(e.target.value);
+ setVisualizationPhase('idle');
+ }
+
+ const currentLpsState = useMemo(() => lpsSteps[currentStep] || {}, [lpsSteps, currentStep]);
+ const currentSearchState = useMemo(() => searchSteps[currentStep] || {}, [searchSteps, currentStep]);
+
+ const isFinalStep = visualizationPhase === 'done';
+
+ return (
+
+
+
+ KMP String Search
+
+
+
+
+ ❓ What is KMP Search Algorithm?
+
+
+
+ The Knuth-Morris-Pratt (KMP) algorithm is an efficient string searching algorithm that finds all occurrences of a pattern in a text string.
+ It avoids unnecessary character comparisons by using information from previous matches.
+
+
+
+ Phase 1 - LPS Table (Longest Proper Prefix which is also Suffix): Preprocesses the pattern to build an LPS array that stores the length of the longest proper prefix that is also a suffix for each position in the pattern. This helps determine how far we can skip characters when a mismatch occurs.
+
+
+ Phase 2 - Pattern Searching: Uses the LPS table to search for the pattern in the text. When a mismatch occurs, instead of starting from the beginning of the pattern, it uses the LPS values to skip unnecessary comparisons, making the search linear time O(n + m) instead of O(n*m).
+
+
+
+
+
+
+
+ Text:
+
+
+
+ Pattern:
+
+
+
+ Re-Visualize
+
+
+
+ {visualizationPhase !== 'idle' ? (
+ <>
+
+
+ {isFinalStep ? "Replay ▶️" : isPlaying ? "Pause ⏸️" : "Play ▶️"}
+
+
+
+ 0 || visualizationPhase === 'search') ? "bg-purple-600 hover:bg-purple-700 text-white" : "bg-gray-600 text-gray-400 cursor-not-allowed"}`}
+ onClick={handlePrev}
+ disabled={currentStep === 0 && visualizationPhase === 'lps'}
+ >
+ < Prev
+
+ handleNext(false)}
+ disabled={isFinalStep}
+ >
+ Next >
+
+
+
+
+ Speed:
+ setSpeed(Number(e.target.value))}
+ >
+ {Object.entries(SPEED_OPTIONS).map(([label, ms]) => (
+ {label}
+ ))}
+
+
+
+
+
+
+ {visualizationPhase === 'lps' && `Phase 1 (LPS): Step ${currentStep + 1} / ${lpsSteps.length}`}
+ {visualizationPhase === 'search' && `Phase 2 (Search): Step ${currentStep + 1} / ${searchSteps.length}`}
+ {visualizationPhase === 'done' && `Visualization Complete!`}
+
+
+
+
+ {visualizationPhase === 'lps' && (
+
+ )}
+ {(visualizationPhase === 'search' || visualizationPhase === 'done') && (
+
+ )}
+
+ {isFinalStep && (
+
+
+ 🎉
+ Search Finished: {matches.length} match(es) found at indices: {matches.join(', ')}
+
+
+ )}
+
+ >
+ ) : (
+
+
Enter text and a pattern, then click "Re-Visualize" to begin.
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx b/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx
index 63f93ac..e263170 100644
--- a/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx
+++ b/src/pages/dynamic-programming/DyanmicProgrammingPage.jsx
@@ -10,6 +10,7 @@ import LISPage from "./LIS";
import CoinChange from "./CoinChange";
import RodCutting from "./RodCutting";
+import KMPSearch from "./KMPSearch";
export default function DynamicProgrammingPage() {
const [selectedAlgo, setSelectedAlgo] = useState("");
@@ -71,6 +72,12 @@ export default function DynamicProgrammingPage() {
);
+ case "KMPSearch":
+ return (
+
+
+
+ );
default:
return (
@@ -138,6 +145,7 @@ export default function DynamicProgrammingPage() {
Longest Increasing Subsequence
Coin Change
Rod Cutting
+ KMP Search
;
+}
\ No newline at end of file