Solving Contains Duplicate in Python: Step-by-Step Guide
The "Contains Duplicate" problem is one of the most fundamental algorithmic challenges you'll encounter, whether you're preparing for coding interviews or building real-world applications that require data validation. At its core, the problem asks a simple question: given an array of integers, does any value appear at least twice? While the question is straightforward, the way you solve it reveals a lot about your understanding of time and space complexity trade-offs. In this tutorial, we'll walk through multiple approaches, from the naive brute-force method to optimized hash-based solutions, and discuss when each is appropriate.
What Is the Contains Duplicate Problem?
The problem statement is typically framed like this: given an integer array nums, return true if any value appears at least twice in the array, and false if every element is distinct. For example, given the input [1, 2, 3, 1], the answer is true because the value 1 appears twice. Given [1, 2, 3, 4], the answer is false because all elements are unique.
This problem is a gateway to understanding how data structures like hash sets and sorting algorithms can dramatically improve performance. It also appears as a building block in more complex problems such as "Contains Duplicate II" (which checks for duplicates within a given index distance) and "Contains Duplicate III" (which adds a value difference constraint).
Why It Matters
Duplicate detection is not just an academic exercise. In real-world software development, you frequently need to verify data integrity, detect anomalies, or enforce uniqueness constraints before persisting records to a database. For instance, when processing user submissions, you might need to ensure no duplicate email addresses exist in a batch import. Understanding the trade-offs between different approaches helps you write code that scales gracefully as your dataset grows from hundreds to millions of records.
Additionally, this problem is a favorite in technical interviews because it cleanly separates candidates who can write working code from those who can write efficient code. A solution that works fine on ten elements might become unusably slow on ten million elements, and knowing why is a critical engineering skill.
Approach 1: Brute Force
The most intuitive approach is to compare every pair of elements in the array. If any two elements at different indices are equal, return true. Otherwise, after checking all pairs, return false. This approach requires no additional data structures but comes at a significant performance cost.
def contains_duplicate_brute_force(nums):
n = len(nums)
for i in range(n):
for j in range(i + 1, n):
if nums[i] == nums[j]:
return True
return False
# Example usage
print(contains_duplicate_brute_force([1, 2, 3, 1])) # True
print(contains_duplicate_brute_force([1, 2, 3, 4])) # False
The time complexity here is O(n²) because of the nested loops. The space complexity is O(1) since we only use a couple of index variables. This solution is acceptable only for very small arrays and should generally be avoided in production code or interviews when better alternatives exist.
Approach 2: Sorting First
A more efficient strategy is to sort the array first. Once sorted, any duplicate values will be adjacent to each other. You can then make a single pass through the array, comparing each element with its neighbor. If any two consecutive elements are equal, a duplicate exists.
def contains_duplicate_sorted(nums):
nums.sort()
for i in range(1, len(nums)):
if nums[i] == nums[i - 1]:
return True
return False
# Example usage
print(contains_duplicate_sorted([1, 2, 3, 1])) # True
print(contains_duplicate_sorted([1, 2, 3, 4])) # False
Sorting typically runs in O(n log n) time, and the subsequent linear scan adds O(n), giving an overall time complexity of O(n log n). The space complexity depends on the sorting algorithm; Python's built-in sort() uses Timsort, which requires O(n) auxiliary space in the worst case. This approach is a solid middle ground when you want to avoid the extra memory of a hash set but still need better-than-quadratic performance.
One important caveat: this method mutates the original array. If preserving the input order matters, you should create a copy before sorting.
Approach 3: Using a Hash Set
The most efficient and idiomatic Python solution leverages a hash set. As you iterate through the array, you check whether the current element already exists in the set. If it does, you've found a duplicate and can return true immediately. If not, you add the element to the set and continue. This approach gives you early exit behavior, often returning before scanning the entire array.
def contains_duplicate_set(nums):
seen = set()
for num in nums:
if num in seen:
return True
seen.add(num)
return False
# Example usage
print(contains_duplicate_set([1, 2, 3, 1])) # True
print(contains_duplicate_set([1, 2, 3, 4])) # False
Set lookups and insertions in Python average O(1) time complexity, so the overall time complexity is O(n) in the average case. The space complexity is O(n) in the worst case, since you might need to store every element in the set before finding a duplicate (or confirming none exist). This is the approach most interviewers expect, and it strikes the best balance between readability and performance for general use.
Approach 4: The Pythonic One-Liner
Python's expressive syntax allows you to solve this problem in a single line by comparing the length of the original array with the length of a set created from that array. Since sets automatically discard duplicates, a shorter set length indicates that duplicates were present.
def contains_duplicate_one_liner(nums):
return len(nums) != len(set(nums))
# Example usage
print(contains_duplicate_one_liner([1, 2, 3, 1])) # True
print(contains_duplicate_one_liner([1, 2, 3, 4])) # False
This solution is concise and easy to read, with O(n) time and O(n) space complexity. However, it always processes the entire array, even if a duplicate appears in the first two elements. For large arrays where duplicates are likely to appear early, the iterative set approach from Approach 3 will often be faster in practice due to early termination.
Comparing the Approaches
Here is a quick summary of the trade-offs between the four methods:
- Brute Force: O(n²) time, O(1) space. Simple but impractical for large inputs.
- Sorting: O(n log n) time, O(n) space. Good when memory is constrained and mutation is acceptable.
- Hash Set (iterative): O(n) time, O(n) space. Best general-purpose solution with early exit.
- One-liner: O(n) time, O(n) space. Cleanest code but no early termination.
Best Practices
When implementing duplicate detection in production code, keep the following best practices in mind. First, always consider the expected size and characteristics of your input data. If you're working with streaming data or very large datasets that don't fit in memory, a hash set approach may not be feasible, and you might need probabilistic structures like Bloom filters. Second, be mindful of side effects: the sorting approach mutates the input, which can introduce subtle bugs if the caller doesn't expect it. Third, leverage early termination whenever possible, especially when duplicates are statistically likely to appear early in the data.
It's also worth writing tests that cover edge cases: empty arrays, single-element arrays, arrays where all elements are identical, and arrays where only the last two elements are duplicates. These cases help ensure your implementation is robust.
import unittest
class TestContainsDuplicate(unittest.TestCase):
def test_empty_array(self):
self.assertFalse(contains_duplicate_set([]))
def test_single_element(self):
self.assertFalse(contains_duplicate_set([42]))
def test_all_identical(self):
self.assertTrue(contains_duplicate_set([7, 7, 7, 7]))
def test_duplicate_at_end(self):
self.assertTrue(contains_duplicate_set([1, 2, 3, 4, 1]))
def test_no_duplicates(self):
self.assertFalse(contains_duplicate_set([1, 2, 3, 4, 5]))
if __name__ == "__main__":
unittest.main()
Conclusion
The Contains Duplicate problem is a deceptively simple challenge that opens the door to deeper discussions about algorithmic efficiency, data structure selection, and practical trade-offs. While the brute-force solution demonstrates basic problem-solving ability, the hash set approach represents the kind of thinking that scales in real applications. By understanding each method's strengths and weaknesses, you'll be equipped not only to ace interviews but also to write Python code that performs reliably under real-world workloads. The next time you encounter a uniqueness check in your projects, you'll know exactly which tool to reach for and why.