Problem
Given an integer array
nums, returntrueif any value appears more than once in the array, otherwise returnfalse.
Example 1
Input: nums = [1, 2, 3, 3]
Output: true
Example 2
Input: nums = [1, 2, 3, 4]
Output: false
Constraints
0 <= len(nums) <= 10^5-10^9 <= nums[i] <= 10^9
Thinking
- Brute force idea: for each element, scan the rest of the list for the same value → . With that is ~ comparisons — too slow.
- Key insight: the question is really “have I seen this value before?”. A
setanswers that in average time, so one pass is enough. - Edge cases to remember: empty list, single element, duplicates at the very end, negative numbers, large values.
Solution
Approach A — my first solution (pop + in)
Pop the first element and check whether it still appears in the remaining list.
Complexity: Time · Space
Why it’s slow — two hidden linear costs per loop:
nums.pop(0)is : removing the front of a Python list shifts every other element left. (pop()from the end is .)temp in numsis :inon a list is a linear scan. (inon a set is .)
It also empties the caller’s list as a side effect.
def has_duplicate_mine(nums: list[int]) -> bool:
while nums:
temp = nums.pop(0) # O(n): shifts the rest of the list
if temp in nums: # O(n): linear scan of a list
return True
return False
Approach B — sort, then compare neighbours
After sorting, equal values sit next to each other.
Complexity: Time · Space for sorted (or extra with in-place nums.sort(), which mutates the input).
Useful when memory is tight.
def has_duplicate_sort(nums: list[int]) -> bool:
s = sorted(nums)
for i in range(1, len(s)):
if s[i] == s[i - 1]:
return True
return False
Approach C — hash set, one pass ✅ best practice
Keep a set of values seen so far; return True the moment one repeats.
Complexity: Time · Space . Stops early on the first duplicate.
def has_duplicate(nums: list[int]) -> bool:
seen = set()
for x in nums:
if x in seen:
return True
seen.add(x)
return False
Approach D — Pythonic one-liner
A set drops duplicates, so if it’s smaller than the list, something repeated.
Complexity: Time · Space . Short and fast in practice (the loop runs in C), but it always builds the whole set — no early exit.
def has_duplicate_len(nums: list[int]) -> bool:
return len(set(nums)) != len(nums)
Scratch / Experiments
Timing on a worst case: no duplicates, so every approach has to look at everything. (Approach A is limited to 10,000 elements — at it would take far longer.)
big = list(range(10_000))
%timeit -n 1 -r 1 has_duplicate_mine(list(big))
%timeit has_duplicate_sort(big)
%timeit has_duplicate(big)
%timeit has_duplicate_len(big)
Takeaways
- Pattern: “have I seen this before?” / uniqueness / counting → hash set (or
dict/collections.Counterwhen you need counts or indices). Trade memory for lookups. Fallback when memory matters: sort first, then duplicates are neighbours. - Mistakes I made:
list.pop(0)is — usecollections.deque.popleft()if you really need to pop from the front.x in some_listis ;x in some_setis . Pick the container based on the operation.- Mutated the input list — avoid side effects unless the problem allows it.
- Related problems: Two Sum (#1, same “seen” idea with a dict), Contains Duplicate II (#219, sliding-window set), Valid Anagram (#242, counting), Longest Consecutive Sequence (#128, set lookups), Find the Duplicate Number (#287, space twist).