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:
+
+
+
Current Element
+
+
+
+
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).