Contains Duplicate (LC 217)
On this page
Pattern: Hashing / Frequency Maps
Difficulty: Easy
Key Concept: A duplicate exists if we ever see a value we have already stored in a set (or if any frequency exceeds one).
Problem Statement
Given an integer array nums, return true if any value appears at least twice in the array, and return false if every element is distinct.
Input
nums— integer array
Output
trueif there is at least one duplicate, elsefalse
1. Algorithm & Pseudocode
Brute force (nested loops)
FOR i from 0 to nums.length - 1:
FOR j from i + 1 to nums.length - 1:
IF nums[i] == nums[j]:
RETURN true
RETURN false
Optimal (HashSet)
CREATE empty HashSet seen
FOR each x in nums:
IF seen.add(x) returns false: // already present
RETURN true
RETURN false
Middle ground (sort)
SORT nums (copy if you must not mutate)
FOR i from 1 to length - 1:
IF nums[i] == nums[i - 1]:
RETURN true
RETURN false
2. Step-by-Step Analysis (Beginner-Friendly)
Why nested loops work?
We compare every pair. If any pair matches, we have a duplicate. This is correct but slow for large (n).
Why use a HashSet?
A set remembers which values we have seen. Each lookup/insert is (O(1)) on average, so one pass over the array is enough.
Why does add returning false mean duplicate?
HashSet.add(e) returns true if e was not already in the set, and false if it was. So false immediately signals a duplicate—no separate contains call.
Why sorting works?
Equal values become adjacent after sorting, so one linear scan finds duplicates. Trade-off: (O(n \log n)) time, often less extra memory than a set if you sort in place (but mutates the array).
3. The Dry Run
Sample: nums = [1, 2, 3, 1]
Optimal — HashSet seen, using add return value:
| Step | Index | x | seen.add(x) returns | seen (after step) | Action / note |
|---|---|---|---|---|---|
| 1 | 0 | 1 | true (new) | {1} | Continue |
| 2 | 1 | 2 | true (new) | {1, 2} | Continue |
| 3 | 2 | 3 | true (new) | {1, 2, 3} | Continue |
| 4 | 3 | 1 | false (already) | {1, 2, 3} | Return true |
Brute force (first duplicate found): pairs (0,3) both value 1 → duplicate → true.
4. Java Solution
Brute Force
class Solution {
public boolean containsDuplicate(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (nums[i] == nums[j]) {
return true;
}
}
}
return false;
}
}
Time: (O(n^2)).
Space: (O(1)) extra.
Optimal
import java.util.HashSet;
import java.util.Set;
class Solution {
public boolean containsDuplicate(int[] nums) {
Set<Integer> seen = new HashSet<>();
for (int x : nums) {
if (!seen.add(x)) {
return true;
}
}
return false;
}
}
Time: (O(n)) average for hash operations.
Space: (O(n)) for the set in the worst case (all distinct).
Sorting variant (middle ground):
import java.util.Arrays;
class Solution {
public boolean containsDuplicate(int[] nums) {
Arrays.sort(nums);
for (int i = 1; i < nums.length; i++) {
if (nums[i] == nums[i - 1]) {
return true;
}
}
return false;
}
}
Time: (O(n \log n)). Space: (O(1)) or (O(\log n)) sort stack, depending on implementation.
5. The “Java vs. Others” Edge
HashSet.add()returnsboolean: Lets you write a compact loop:if (!seen.add(x)) return true;. Very idiomatic in Java.- C++:
unordered_set::insert(x).secondisfalseifxwas already there; or usecount(x)before insert. - Python:
x in seenthenseen.add(x), or build a set from the list and comparelen(set(nums)) < len(nums). - Sorting: Mutates
numsin the snippet above; clone first if the caller must keep the original order.
6. Complexity Summary
| Approach | Time | Space | Notes |
|---|---|---|---|
| Brute Force | (O(n^2)) | (O(1)) | Nested loops; no extra structure. |
| Optimal (HashSet) | (O(n))* | (O(n)) | *Average case; worst-case hash maps can degrade. |
| Sorting | (O(n \log n)) | (O(1))~ | In-place sort; detects duplicates by adjacent equality. |