TL;DR
Hash set of seen values β O(n) time, O(n) space.
Approach 1 β Brute force
Compare every pair of elements. If any pair matches, there is a duplicate.
from typing import List
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
if nums[i] == nums[j]:
return True
return False
Complexity: O(n^2) time, O(1) space. With n = 10^5 that is roughly 5 billion comparisons β far past the usual ~10^8 budget, so the constraints kill it.
Approach 2 β Sort first
The insight: sorting forces equal values to become neighbors, so after sorting you only need to compare each element with the one right before it β a single linear scan instead of all pairs.
from typing import List
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
ordered = sorted(nums)
for i in range(1, len(ordered)):
if ordered[i] == ordered[i - 1]:
return True
return False
Walkthrough on nums = [1,2,3,1]:
- Sorted:
[1,1,2,3].
i = 1: ordered[1] = 1 equals ordered[0] = 1 β return True immediately.
Complexity: O(n log n) time for the sort, O(n) space for the sorted copy (O(1) extra if you may sort in place). Fast enough, and useful when memory for a hash set is tight.
Approach 3 β Hash set
The insight: you donβt need any ordering at all β you only need to answer βhave I seen this exact value before?β in O(1). A hash set does exactly that, so one pass suffices, and you can stop at the first repeat without touching the rest of the array.
from typing import List
class Solution:
def containsDuplicate(self, nums: List[int]) -> bool:
seen: set[int] = set()
for x in nums:
if x in seen:
return True
seen.add(x)
return False
Walkthrough on nums = [1,2,3,1]:
| x | x in seen? | seen after |
|---|
| 1 | no | {1} |
| 2 | no | {1, 2} |
| 3 | no | {1, 2, 3} |
| 1 | yes | β return True |
For nums = [1,2,3,4] the loop finishes with no hit and returns False.
Complexity: O(n) time on average, O(n) space.
A well-known one-liner variant of the same idea builds the whole set up front β return len(set(nums)) < len(nums) β same O(n)/O(n), but it always processes the entire array instead of stopping at the first duplicate.
Common pitfalls
- Reaching for the O(n^2) double loop under interview pressure β state its cost out loud and immediately trade space for time.
- Sorting the callerβs list in place (
nums.sort()) mutates the input; use sorted(nums) unless mutation is explicitly fine.
- The one-liner
len(set(nums)) < len(nums) is elegant but does not short-circuit β on a 10^5-element array whose first two entries match, the early-exit loop wins.
- Donβt overthink value bounds: Python ints hash fine at any size, but in fixed-width languages the
Β±10^9 range rules out a plain boolean array indexed by value.
Pattern takeaway
This is the purest form of the Arrays & Hashing trade: a question about pairs of positions (βdo any two indices hold the same value?β) becomes a question about membership (βhas this value occurred before?β), which a hash set answers in O(1). Whenever a nested loop exists only to re-find something you already walked past, replace the inner loop with a hash structure keyed on what youβre looking for.