diff --git a/animations/maps/example1.html b/animations/maps/example1.html
new file mode 100644
index 0000000..a1935eb
--- /dev/null
+++ b/animations/maps/example1.html
@@ -0,0 +1,72 @@
+
+
+
+
+ Contains Nearby Duplicate Visualization
+
+
+
+
+
+
+ Contains Nearby Duplicate Using std::map
+ This visualization demonstrates the algorithm that checks if an array contains duplicates within distance k.
+
+
+
+
+
+
+ Select a test case and click "Next Step" to begin the animation.
+
+
+ Result:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Color Key:
+
+
+
+
Previous Occurrence
+
+
+
+
Duplicate Within Distance k
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/animations/maps/script.js b/animations/maps/script.js
new file mode 100644
index 0000000..466441c
--- /dev/null
+++ b/animations/maps/script.js
@@ -0,0 +1,322 @@
+// Define code content with syntax highlighting
+const codeContent = [
+ '0. bool containsNearbyDuplicate(std::vector<int>& nums, int k) {',
+ '1. int size = nums.size();',
+ '2. // create the map, map key is the value of the vector element, map value is the index',
+ '3. std::map<int,int> map1;',
+ '4. for(int i=0; i<size; i++) {',
+ '5. // if already exists',
+ '6. if(map1.find(nums[i]) != map1.end()) {',
+ '7. if(i - map1[nums[i]] <= k) {',
+ '8. return true;',
+ '9. }',
+ '10. }',
+ '11. map1[nums[i]] = i;',
+ '12. }',
+ '13. return false;',
+ '14. }'
+];
+
+// Test cases
+const testCases = [
+ {
+ array: [1, 2, 3, 1],
+ k: 3,
+ description: "Array [1, 2, 3, 1] with k=3: Expected true (nums[0] == nums[3], |0-3| <= 3)"
+ },
+ {
+ array: [1, 0, 1, 1],
+ k: 1,
+ description: "Array [1, 0, 1, 1] with k=1: Expected true (nums[2] == nums[3], |2-3| <= 1)"
+ },
+ {
+ array: [1, 2, 3, 4, 5],
+ k: 2,
+ description: "Array [1, 2, 3, 4, 5] with k=2: Expected false (no duplicates within distance k)"
+ },
+ {
+ array: [1, 2, 3, 4, 1],
+ k: 4,
+ description: "Array [1, 2, 3, 4, 1] with k=4: Expected true (nums[0] == nums[4], |0-4| == 4 <= 4)"
+ },
+ {
+ array: [99, 99],
+ k: 1,
+ description: "Array [99, 99] with k=1: Expected true (nums[0] == nums[1], |0-1| <= 1)"
+ },
+ {
+ array: [1, 2, 3, 4, 5, 6],
+ k: 2,
+ description: "Array [1, 2, 3, 4, 5, 6] with k=2: Expected false (no duplicates)"
+ }
+];
+
+// Global variables to track animation state
+let currentTestCase = 0;
+let currentStep = 0;
+let map = new Map();
+let result = false;
+let animationFinished = false;
+let currentLineHighlighted = -1;
+
+// Initialize code display
+function initCodeDisplay() {
+ const codeDisplay = document.getElementById('codeDisplay');
+ codeContent.forEach((line, index) => {
+ const lineDiv = document.createElement('div');
+ lineDiv.className = 'code-line';
+ lineDiv.setAttribute('data-line', index);
+ lineDiv.innerHTML = line;
+ codeDisplay.appendChild(lineDiv);
+ });
+}
+
+// Highlight specific line of code
+function highlightLine(lineNum) {
+ // Remove current highlight
+ if (currentLineHighlighted >= 0) {
+ const currentLine = document.querySelector(`.code-line[data-line="${currentLineHighlighted}"]`);
+ if (currentLine) {
+ currentLine.classList.remove('highlighted');
+ }
+ }
+
+ // Add new highlight
+ currentLineHighlighted = lineNum;
+ if (lineNum >= 0) {
+ const targetLine = document.querySelector(`.code-line[data-line="${lineNum}"]`);
+ if (targetLine) {
+ targetLine.classList.add('highlighted');
+ targetLine.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }
+ }
+}
+
+// Update test case description
+function updateCaseDescription() {
+ const descEl = document.getElementById('caseDescription');
+ descEl.textContent = testCases[currentTestCase].description;
+}
+
+// Create array visualization
+function createArrayVisualization() {
+ const container = document.getElementById('arrayContainer');
+ container.innerHTML = ''; // Clear container
+
+ const array = testCases[currentTestCase].array;
+ array.forEach((value, index) => {
+ const item = document.createElement('div');
+ item.className = 'array-item';
+ item.setAttribute('data-index', index);
+ item.textContent = value;
+ container.appendChild(item);
+ });
+}
+
+// Update map visualization
+function updateMapVisualization() {
+ const container = document.getElementById('mapContainer');
+ container.innerHTML = ''; // Clear container
+
+ // Display current map entries
+ map.forEach((value, key) => {
+ const item = document.createElement('div');
+ item.className = 'map-item';
+
+ const keyDiv = document.createElement('div');
+ keyDiv.className = 'map-key';
+ keyDiv.textContent = `Key: ${key}`;
+
+ const valueDiv = document.createElement('div');
+ valueDiv.className = 'map-value';
+ valueDiv.textContent = `Index: ${value}`;
+
+ item.appendChild(keyDiv);
+ item.appendChild(valueDiv);
+ container.appendChild(item);
+ });
+}
+
+// Update explanation text
+function updateExplanation(text) {
+ document.getElementById('explanation').textContent = text;
+}
+
+// Update result display
+function updateResult(res, final = false) {
+ const resultEl = document.getElementById('result');
+ resultEl.textContent = res ? "true" : "false";
+
+ if (final) {
+ resultEl.className = res ? "success" : "failure";
+ } else {
+ resultEl.className = "";
+ }
+}
+
+// Highlight array item
+function highlightArrayItem(index, className) {
+ const items = document.querySelectorAll('.array-item');
+ if (index >= 0 && index < items.length) {
+ // Remove previous highlighting of the same class
+ items.forEach(item => {
+ if (item.classList.contains(className)) {
+ item.classList.remove(className);
+ }
+ });
+
+ // Add new highlight
+ items[index].classList.add(className);
+ }
+}
+
+// Remove all highlights from array items
+function clearArrayHighlights() {
+ const items = document.querySelectorAll('.array-item');
+ items.forEach(item => {
+ item.classList.remove('current', 'found', 'duplicate');
+ });
+}
+
+// Reset animation state
+function resetAnimation() {
+ currentStep = 0;
+ map = new Map();
+ result = false;
+ animationFinished = false;
+
+ highlightLine(-1);
+ clearArrayHighlights();
+ updateMapVisualization();
+ updateResult(false);
+ updateExplanation("Animation reset. Click 'Next Step' to begin.");
+
+ document.getElementById('nextStep').disabled = false;
+}
+
+// Handle test case button clicks
+function initTestCaseButtons() {
+ const buttons = document.querySelectorAll('.test-btn');
+ buttons.forEach(button => {
+ button.addEventListener('click', function() {
+ // Update active button
+ buttons.forEach(btn => btn.classList.remove('active'));
+ this.classList.add('active');
+
+ // Set current test case
+ currentTestCase = parseInt(this.getAttribute('data-testcase'));
+
+ // Reset and initialize visualization
+ resetAnimation();
+ updateCaseDescription();
+ createArrayVisualization();
+ });
+ });
+}
+
+// Animation steps for containsNearbyDuplicate algorithm
+async function runAnimationStep() {
+ const array = testCases[currentTestCase].array;
+ const k = testCases[currentTestCase].k;
+
+ if (animationFinished) {
+ updateExplanation("Animation complete. Choose another test case or click 'Reset' to run again.");
+ return;
+ }
+
+ switch (currentStep) {
+ case 0:
+ // Function definition
+ highlightLine(0);
+ updateExplanation(`Starting containsNearbyDuplicate function with array [${array.join(', ')}] and k=${k}`);
+ break;
+
+ case 1:
+ // Get array size
+ highlightLine(1);
+ updateExplanation(`Getting array size: size = ${array.length}`);
+ break;
+
+ case 2:
+ // Create map
+ highlightLine(3);
+ updateExplanation("Creating an empty map to store values and their indices");
+ break;
+
+ default:
+ // Handle main loop iterations
+ const loopIndex = Math.floor((currentStep - 3) / 3);
+ const stepInLoop = (currentStep - 3) % 3;
+
+ if (loopIndex >= array.length) {
+ // Loop finished, return result
+ highlightLine(13);
+ updateExplanation("No duplicates found within distance k. Returning false.");
+ updateResult(false, true);
+ animationFinished = true;
+ break;
+ }
+
+ const currentValue = array[loopIndex];
+
+ if (stepInLoop === 0) {
+ // Start of loop iteration
+ highlightLine(4);
+ updateExplanation(`Starting iteration ${loopIndex}: Processing element nums[${loopIndex}] = ${currentValue}`);
+ clearArrayHighlights();
+ highlightArrayItem(loopIndex, 'current');
+ }
+ else if (stepInLoop === 1) {
+ // Check if value exists in map
+ highlightLine(6);
+ const exists = map.has(currentValue);
+ updateExplanation(`Checking if ${currentValue} already exists in the map: ${exists ? 'Found' : 'Not found'}`);
+
+ if (exists) {
+ const prevIndex = map.get(currentValue);
+ highlightArrayItem(prevIndex, 'found');
+
+ // Check if within k distance
+ highlightLine(7);
+ const diff = loopIndex - prevIndex;
+ const withinK = diff <= k;
+
+ if (withinK) {
+ // Found duplicate within k distance
+ highlightLine(8);
+ updateExplanation(`Found duplicate! nums[${prevIndex}] = nums[${loopIndex}] = ${currentValue}, and |${loopIndex} - ${prevIndex}| = ${diff} <= ${k}. Returning true.`);
+ updateResult(true, true);
+ highlightArrayItem(loopIndex, 'duplicate');
+ animationFinished = true;
+ break;
+ } else {
+ updateExplanation(`nums[${prevIndex}] = nums[${loopIndex}] = ${currentValue}, but |${loopIndex} - ${prevIndex}| = ${diff} > ${k}. Continue checking.`);
+ }
+ }
+ }
+ else if (stepInLoop === 2) {
+ // Update map
+ highlightLine(11);
+ map.set(currentValue, loopIndex);
+ updateMapVisualization();
+ updateExplanation(`Updating map: map[${currentValue}] = ${loopIndex}`);
+ }
+ }
+
+ currentStep++;
+}
+
+// Initialize on page load
+window.onload = function() {
+ initCodeDisplay();
+ initTestCaseButtons();
+ updateCaseDescription();
+ createArrayVisualization();
+
+ // Set up event listeners
+ document.getElementById('nextStep').addEventListener('click', runAnimationStep);
+ document.getElementById('resetBtn').addEventListener('click', function() {
+ resetAnimation();
+ createArrayVisualization();
+ });
+};
\ No newline at end of file
diff --git a/animations/maps/style.css b/animations/maps/style.css
new file mode 100644
index 0000000..1a21b8c
--- /dev/null
+++ b/animations/maps/style.css
@@ -0,0 +1,365 @@
+html, body {
+ margin: 0;
+ padding: 0;
+ font-family: Arial, sans-serif;
+ font-size: 16px;
+ overflow-x: hidden;
+ }
+
+ h1 {
+ text-align: center;
+ margin-top: 12px;
+ margin-bottom: 8px;
+ font-size: 26px;
+ }
+
+ p {
+ text-align: center;
+ margin: 8px 0 12px;
+ font-size: 15px;
+ }
+
+ .visualization-container {
+ display: flex;
+ justify-content: space-between;
+ max-width: 1800px;
+ margin: 0 auto;
+ height: 80vh;
+ gap: 20px;
+ padding: 0 15px;
+ }
+
+ .left-panel {
+ display: flex;
+ flex-direction: column;
+ width: 48%;
+ }
+
+ .code-container {
+ background-color: #f0f0f0;
+ border-radius: 10px;
+ padding: 15px;
+ flex-grow: 1;
+ overflow-y: auto;
+ max-height: 52vh;
+ font-size: 16px;
+ margin-bottom: 15px;
+ }
+
+ .explanation-result-container {
+ background-color: #f0f0f0;
+ border-radius: 10px;
+ padding: 15px;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .color-key {
+ background-color: #f9f9f9;
+ border-radius: 5px;
+ padding: 10px;
+ margin-top: 10px;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-around;
+ align-items: center;
+ }
+
+ .key-item {
+ display: flex;
+ align-items: center;
+ margin: 4px 12px;
+ }
+
+ .key-color {
+ width: 18px;
+ height: 18px;
+ border-radius: 3px;
+ margin-right: 8px;
+ }
+
+ .key-current {
+ background-color: #fffde7;
+ border: 2px solid #ffeb3b;
+ }
+
+ .key-found {
+ background-color: #e8f5e9;
+ border: 2px solid #4caf50;
+ }
+
+ .key-duplicate {
+ background-color: #ffebee;
+ border: 2px solid #f44336;
+ }
+
+ .key-description {
+ font-size: 14px;
+ }
+
+ pre {
+ margin: 0;
+ background-color: #f0f0f0;
+ width: 100%;
+ }
+
+ code {
+ font-family: Consolas, "Courier New", monospace;
+ white-space: pre;
+ display: inline-block;
+ min-width: 700px;
+ }
+
+ #codeDisplay {
+ width: 100%;
+ }
+
+ .line-number {
+ color: #666;
+ display: inline-block;
+ min-width: 35px;
+ font-size: 16px;
+ }
+
+ .code-line {
+ display: block;
+ width: 100%;
+ position: relative;
+ box-sizing: border-box;
+ padding: 2px 0;
+ font-size: 16px;
+ line-height: 1.5;
+ }
+
+ .code-line.highlighted {
+ background-color: #ffeb3b;
+ width: 100%;
+ display: block;
+ }
+
+ .visualization-panel {
+ background-color: #f0f0f0;
+ border-radius: 10px;
+ padding: 15px;
+ width: 48%;
+ height: 75vh;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .top-controls {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 12px;
+ }
+
+ .test-case-btns {
+ display: flex;
+ justify-content: center;
+ gap: 6px;
+ flex-wrap: wrap;
+ flex: 1;
+ }
+
+ .test-btn {
+ padding: 6px 8px;
+ font-size: 14px;
+ border: none;
+ border-radius: 4px;
+ background-color: #e0e0e0;
+ cursor: pointer;
+ }
+
+ .test-btn:hover {
+ background-color: #d0d0d0;
+ }
+
+ .test-btn.active {
+ background-color: #2196f3;
+ color: white;
+ }
+
+ .case-description {
+ background-color: #e8e8e8;
+ padding: 10px;
+ border-radius: 5px;
+ margin-bottom: 12px;
+ font-size: 15px;
+ text-align: center;
+ line-height: 1.4;
+ }
+
+ .controls {
+ display: flex;
+ justify-content: center;
+ gap: 15px;
+ margin-bottom: 12px;
+ }
+
+ #nextStep, #resetBtn {
+ padding: 8px 15px;
+ font-size: 16px;
+ cursor: pointer;
+ background-color: #4CAF50;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ }
+
+ #resetBtn {
+ background-color: #f44336;
+ }
+
+ #nextStep:hover {
+ background-color: #45a049;
+ }
+
+ #resetBtn:hover {
+ background-color: #d32f2f;
+ }
+
+ #nextStep:disabled, #resetBtn:disabled {
+ background-color: #cccccc;
+ cursor: not-allowed;
+ }
+
+ .visualization-area {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ overflow: hidden;
+ padding: 15px;
+ background-color: white;
+ border-radius: 5px;
+ margin-bottom: 12px;
+ max-height: 55vh;
+ flex-grow: 1;
+ padding-bottom: 30px;
+ }
+
+ .array-container, .map-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 15px 10px 28px 10px;
+ background-color: #f9f9f9;
+ border-radius: 5px;
+ min-height: 80px;
+ position: relative;
+ margin-top: 22px;
+ }
+
+ .array-container::before, .map-container::before {
+ content: "Array";
+ position: absolute;
+ top: -20px;
+ left: 10px;
+ font-weight: bold;
+ font-size: 15px;
+ }
+
+ .map-container::before {
+ content: "Map";
+ }
+
+ .array-item, .map-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 55px;
+ height: 55px;
+ background-color: white;
+ border: 2px solid #ddd;
+ border-radius: 5px;
+ font-weight: bold;
+ font-size: 16px;
+ transition: all 0.3s ease;
+ }
+
+ .array-item {
+ position: relative;
+ }
+
+ .array-item::after {
+ content: attr(data-index);
+ position: absolute;
+ bottom: -20px;
+ font-size: 13px;
+ color: #666;
+ }
+
+ .map-item {
+ width: 90px;
+ height: 70px;
+ margin: 4px;
+ }
+
+ .map-key {
+ font-size: 15px;
+ margin-bottom: 5px;
+ }
+
+ .map-value {
+ font-size: 13px;
+ color: #666;
+ }
+
+ .current {
+ border-color: #ffeb3b;
+ background-color: #fffde7;
+ box-shadow: 0 0 5px #ffeb3b;
+ }
+
+ .found {
+ border-color: #4caf50;
+ background-color: #e8f5e9;
+ box-shadow: 0 0 5px #4caf50;
+ }
+
+ .duplicate {
+ border-color: #f44336;
+ background-color: #ffebee;
+ box-shadow: 0 0 5px #f44336;
+ }
+
+ .explanation {
+ font-size: 15px;
+ padding: 12px;
+ background-color: #e8e8e8;
+ border-radius: 5px;
+ min-height: 60px;
+ line-height: 1.4;
+ margin-bottom: 12px;
+ overflow-y: auto;
+ max-height: 100px;
+ }
+
+ .result-container {
+ padding: 12px;
+ background-color: #e8e8e8;
+ border-radius: 5px;
+ text-align: center;
+ font-weight: bold;
+ font-size: 16px;
+ }
+
+ #result {
+ color: #2196f3;
+ font-size: 18px;
+ }
+
+ .success {
+ color: #4caf50 !important;
+ }
+
+ .failure {
+ color: #f44336 !important;
+ }
+
+ .color-key-label {
+ font-weight: bold;
+ margin-bottom: 5px;
+ font-size: 15px;
+ }
\ No newline at end of file
diff --git a/lectures/15_maps_I/README.md b/lectures/15_maps_I/README.md
index 7701ecf..2f05ccb 100644
--- a/lectures/15_maps_I/README.md
+++ b/lectures/15_maps_I/README.md
@@ -233,6 +233,8 @@ Test Case 6: false
You can also find this problem on Leetcode.
- [Leetcode problem 219: Contains Duplicate II](https://leetcode.com/problems/contains-duplicate-ii/description/). Solution: [p219_contains_duplicate_ii.cpp](../../leetcode/p219_contains_duplicate_ii.cpp).
+- Play this [animation](https://jidongxiao.github.io/CSCI1200-DataStructures/animations/maps/example1.html) to understand how this program works.
+
## 15.11 More Leetcode Exercises
- [Leetcode problem 1: Two Sum](https://leetcode.com/problems/two-sum/). Solution: [p1_twosum.cpp](../../leetcode/p1_twosum.cpp).