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:
- The values at these indices are equal:
nums[i] == nums[j]
- 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]
andk = 3
, the answer would betrue
becausenums[0] == nums[3]
(both equal to 1) andabs(0 - 3) = 3 <= 3
. - If
nums = [1, 2, 3, 1, 2, 3]
andk = 2
, the answer would befalse
because while there are duplicates, none of them are within a distance of 2 from each other.
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:
- For each element, we check if we've seen it before (exists in hash table)
- If yes, we calculate the distance from its most recent occurrence
- If the distance is
<= k
, we found our answer - 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:
-
Initialize an empty dictionary
d = {}
to store elements and their indices. -
Iterate through the array using
enumerate(nums)
to get both indexi
and valuex
simultaneously. -
For each element
x
at indexi
:- Check if
x
already exists in the dictionaryd
- If it exists AND the distance condition is satisfied:
i - d[x] <= k
- Return
True
immediately (we found a valid pair)
- Return
- Otherwise, update the dictionary:
d[x] = i
- This stores/updates the most recent index for value
x
- This stores/updates the most recent index for value
- Check if
-
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, soi > d[x]
is guaranteed, makingi - 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 EvaluatorExample 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
ind
? No - Update:
d[1] = 0
- Current state:
d = {1: 0}
Step 2: Index i = 1
, value x = 0
- Check: Is
0
ind
? No - Update:
d[0] = 1
- Current state:
d = {1: 0, 0: 1}
Step 3: Index i = 2
, value x = 1
- Check: Is
1
ind
? Yes! (at index 0) - Calculate distance:
i - d[1] = 2 - 0 = 2
- Is
2 <= k
(wherek = 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
ind
? Yes! (at index 2) - Calculate distance:
i - d[1] = 3 - 2 = 1
- Is
1 <= k
(wherek = 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.
Which type of traversal does breadth first search do?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!