Facebook Pixel

219. Contains Duplicate II

Problem Description

You are given an integer array nums and an integer k. Your task is to determine if there exist two distinct indices i and j in the array that satisfy both of the following conditions:

  1. The values at these indices are equal: nums[i] == nums[j]
  2. The absolute difference between the indices is at most k: abs(i - j) <= k

In other words, you need to find if there are any duplicate values in the array where the duplicates appear within a distance of at most k positions from each other.

Return true if such a pair of indices exists, otherwise return false.

For example:

  • If nums = [1, 2, 3, 1] and k = 3, the answer would be true because nums[0] == nums[3] (both equal to 1) and abs(0 - 3) = 3 <= 3.
  • If nums = [1, 2, 3, 1, 2, 3] and k = 2, the answer would be false because while there are duplicates, none of them are within a distance of 2 from each other.
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When looking for duplicate values within a certain distance, we need to keep track of elements we've seen recently along with their positions. The key insight is that we don't need to remember all occurrences of each element - we only need to remember the most recent occurrence.

Why? Because if we're at index i and we've seen the same value before at indices j1, j2, j3, ... (where j1 < j2 < j3 < ... < i), we only need to check if the distance from the most recent occurrence satisfies our condition. If i - j3 > k, then certainly i - j2 > k and i - j1 > k as well, since j3 is the closest previous occurrence to i.

This leads us to use a hash table where:

  • The key is the element value
  • The value is the most recent index where we saw that element

As we traverse the array:

  1. For each element, we check if we've seen it before (exists in hash table)
  2. If yes, we calculate the distance from its most recent occurrence
  3. If the distance is <= k, we found our answer
  4. Otherwise (or if we haven't seen it before), we update the hash table with the current index as the new "most recent occurrence"

This way, we only need a single pass through the array with O(n) time complexity and O(min(n, k)) space complexity for the hash table.

Solution Approach

We implement the solution using a hash table to track the most recent index of each element as we traverse the array.

Data Structure:

  • Hash table d where keys are element values and values are their most recent indices

Algorithm Steps:

  1. Initialize an empty dictionary d = {} to store elements and their indices.

  2. Iterate through the array using enumerate(nums) to get both index i and value x simultaneously.

  3. For each element x at index i:

    • Check if x already exists in the dictionary d
    • If it exists AND the distance condition is satisfied: i - d[x] <= k
      • Return True immediately (we found a valid pair)
    • Otherwise, update the dictionary: d[x] = i
      • This stores/updates the most recent index for value x
  4. If we complete the iteration without finding any valid pair, return False.

Key Implementation Details:

  • The condition x in d and i - d[x] <= k performs both checks:

    • First checks if we've seen this value before
    • Then checks if the distance from the previous occurrence is within k
  • We don't need abs(i - j) because we're always comparing with a previous index, so i > d[x] is guaranteed, making i - d[x] always positive.

  • By constantly updating d[x] = i, we ensure that the dictionary always contains the most recent index for each value, which is optimal for finding the closest duplicate.

Time Complexity: O(n) - single pass through the array
Space Complexity: O(min(n, k)) - the hash table stores at most n unique elements

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [1, 0, 1, 1] and k = 1.

We'll use a hash table d to track the most recent index of each element.

Initial state: d = {}

Step 1: Index i = 0, value x = 1

  • Check: Is 1 in d? No
  • Update: d[1] = 0
  • Current state: d = {1: 0}

Step 2: Index i = 1, value x = 0

  • Check: Is 0 in d? No
  • Update: d[0] = 1
  • Current state: d = {1: 0, 0: 1}

Step 3: Index i = 2, value x = 1

  • Check: Is 1 in d? Yes! (at index 0)
  • Calculate distance: i - d[1] = 2 - 0 = 2
  • Is 2 <= k (where k = 1)? No, distance is too large
  • Update: d[1] = 2 (replace old index with current one)
  • Current state: d = {1: 2, 0: 1}

Step 4: Index i = 3, value x = 1

  • Check: Is 1 in d? Yes! (at index 2)
  • Calculate distance: i - d[1] = 3 - 2 = 1
  • Is 1 <= k (where k = 1)? Yes!
  • Return True - we found duplicates within distance k

The algorithm correctly identifies that nums[2] = nums[3] = 1 and their distance |2 - 3| = 1 satisfies the constraint 1 <= k.

Note how updating the hash table at Step 3 was crucial - it allowed us to find the closer duplicate pair (indices 2 and 3) instead of checking the farther pair (indices 0 and 3).

Solution Implementation

1class Solution:
2    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
3        # Dictionary to store the most recent index of each element
4        index_map = {}
5      
6        # Iterate through the array with index and value
7        for current_index, value in enumerate(nums):
8            # Check if we've seen this value before and if the distance is within k
9            if value in index_map and current_index - index_map[value] <= k:
10                # Found a duplicate within distance k
11                return True
12          
13            # Update the dictionary with the current index for this value
14            # This overwrites any previous index, keeping only the most recent
15            index_map[value] = current_index
16      
17        # No duplicates found within distance k
18        return False
19
1class Solution {
2    public boolean containsNearbyDuplicate(int[] nums, int k) {
3        // HashMap to store each number and its most recent index
4        Map<Integer, Integer> numToIndexMap = new HashMap<>();
5      
6        // Iterate through the array
7        for (int currentIndex = 0; currentIndex < nums.length; currentIndex++) {
8            // Get the previous index of the current number (default to a very small value if not found)
9            int previousIndex = numToIndexMap.getOrDefault(nums[currentIndex], -1000000);
10          
11            // Check if the distance between current and previous occurrence is at most k
12            if (currentIndex - previousIndex <= k) {
13                return true;  // Found duplicate within distance k
14            }
15          
16            // Update the map with the current number and its latest index
17            numToIndexMap.put(nums[currentIndex], currentIndex);
18        }
19      
20        // No duplicates found within distance k
21        return false;
22    }
23}
24
1class Solution {
2public:
3    bool containsNearbyDuplicate(vector<int>& nums, int k) {
4        // Hash map to store each number and its most recent index
5        unordered_map<int, int> numToIndex;
6      
7        // Iterate through all elements in the array
8        for (int i = 0; i < nums.size(); ++i) {
9            // Check if current number exists in the map and 
10            // the distance between current index and previous index is at most k
11            if (numToIndex.count(nums[i]) && i - numToIndex[nums[i]] <= k) {
12                return true;
13            }
14          
15            // Update the map with current number and its index
16            // This overwrites the previous index if the number existed
17            numToIndex[nums[i]] = i;
18        }
19      
20        // No duplicate found within distance k
21        return false;
22    }
23};
24
1/**
2 * Checks if there are two distinct indices i and j in the array such that
3 * nums[i] == nums[j] and the absolute difference between i and j is at most k
4 * @param nums - The input array of numbers
5 * @param k - The maximum distance between duplicate elements
6 * @returns true if nearby duplicates exist within distance k, false otherwise
7 */
8function containsNearbyDuplicate(nums: number[], k: number): boolean {
9    // Map to store each number and its most recent index
10    const numberToIndexMap: Map<number, number> = new Map<number, number>();
11  
12    // Iterate through each element in the array
13    for (let currentIndex = 0; currentIndex < nums.length; currentIndex++) {
14        const currentNumber = nums[currentIndex];
15      
16        // Check if we've seen this number before
17        if (numberToIndexMap.has(currentNumber)) {
18            const previousIndex = numberToIndexMap.get(currentNumber)!;
19            const distance = currentIndex - previousIndex;
20          
21            // If the distance between duplicates is within k, return true
22            if (distance <= k) {
23                return true;
24            }
25        }
26      
27        // Update the map with the current number and its latest index
28        numberToIndexMap.set(currentNumber, currentIndex);
29    }
30  
31    // No nearby duplicates found
32    return false;
33}
34

Time and Space Complexity

The time complexity is O(n), where n is the length of the array nums. This is because the algorithm iterates through the array exactly once using a single for loop. Each operation inside the loop (checking if a key exists in the dictionary, accessing a dictionary value, updating a dictionary entry) takes O(1) average time for hash table operations.

The space complexity is O(min(n, k+1)) in the best case, but O(n) in the worst case, where n is the length of the array nums. The dictionary d stores at most one entry for each unique element in the array. In the worst case where all elements are unique, the dictionary will store n key-value pairs. However, if we consider the sliding window nature of the problem, we could optimize to maintain at most k+1 elements in the dictionary at any time, though this implementation doesn't do that optimization.

Common Pitfalls

Pitfall 1: Using a Set Instead of a Dictionary

Problem: A common mistake is trying to use a set to track seen elements without storing their indices.

# INCORRECT approach
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
    seen = set()
    for i, x in enumerate(nums):
        if x in seen:
            return True  # But we don't know if it's within distance k!
        seen.add(x)
    return False

Why it fails: This approach can detect duplicates but cannot verify the distance constraint since we don't track where the previous occurrence was.

Solution: Always use a dictionary/hash map to store both the value and its most recent index.

Pitfall 2: Maintaining a Sliding Window of Size k

Problem: Some might try to maintain a sliding window using a set, removing elements that are more than k positions away:

# INEFFICIENT approach
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
    window = set()
    for i, x in enumerate(nums):
        if x in window:
            return True
        window.add(x)
        if len(window) > k:
            window.remove(nums[i - k])  # Remove element that's now too far
    return False

Why it's problematic:

  • This works but is less intuitive and harder to understand
  • Requires careful management of the window size
  • The logic for when to remove elements can be error-prone (off-by-one errors)

Solution: The dictionary approach with index tracking is cleaner and more straightforward.

Pitfall 3: Not Updating the Index for Repeated Elements

Problem: Forgetting to update the index when encountering the same element again:

# INCORRECT - missing index update
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
    d = {}
    for i, x in enumerate(nums):
        if x in d:
            if i - d[x] <= k:
                return True
            # MISSING: d[x] = i  (forgot to update!)
        else:
            d[x] = i
    return False

Why it fails: Consider nums = [1, 0, 1, 1] and k = 1. After checking indices 0 and 2 (both have value 1), if we don't update the index, we miss the valid pair at indices 2 and 3.

Solution: Always update d[x] = i regardless of whether the distance check passes or fails. This ensures we're always comparing with the closest previous occurrence.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which type of traversal does breadth first search do?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More