Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 19

Hash Sets: Duplicate Detection & Membership Testing

Recommended resources

Hello! Welcome to your next lesson on hashing.

In our last lesson, we mastered the "Two Sum" problem using hash maps to store key-value pairs (value -> index) for instant lookups. This demonstrated a powerful time-space tradeoff and the core idea of searching for a "complement."

Today, we'll focus on a simpler but equally powerful hash-based structure: the hash set. Your learning outcome is to use hash sets for efficient duplicate detection and membership testing. While a hash map stores pairs, a hash set stores only unique items, making it the perfect tool when all you need to know is whether you've seen an item before. This is a fundamental pattern for many interview problems.

1. What is a Hash Set?

A hash set is a data structure that stores a collection of unique items. Think of it as a hash map where you only care about the keys, not the values. Its primary purpose is to answer one question extremely quickly: "Is this item in the collection?"

The three main operations on a hash set are:

  • Add: Adds an item to the set. If the item is already there, nothing changes.
  • Contains: Checks if an item exists in the set.
  • Remove: Deletes an item from the set.

Because hash sets are built on top of hash tables, these operations have an average time complexity of O(1).

Hash Tables: Hash Functions, Sets & Maps

To start, let's get a formal definition. The article 'Hash Tables: Hash Functions, Sets & Maps' provides a concise explanation of what a hash set is, its implementation in Python, and its performance characteristics.

Please read the sections titled 'Hash Sets: Unique Data Storage', 'Python’s Built-in Hash Structures', and 'Performance and Use Cases'. Focus on how a hash set guarantees uniqueness and provides O(1) average time complexity for its core operations.

How do Hash Sets achieve O(1) speed?

The "magic" of O(1) average time complexity comes from the underlying hash table. To build your intuition, it's helpful to see how one works under the hood. When you add an item, the set:

  1. Computes a hash code for the item (an integer).
  2. Uses this hash code to calculate an index into an underlying array.
  3. Stores the item at that index.

When you check if an item is in the set, it repeats steps 1 and 2 to instantly find the correct index and see if the item is there, without needing to scan the entire collection.

Python Hash Sets Explained & Demonstrated - Computerphile

This video from Computerphile gives an excellent visual demonstration of how a hash set is built from scratch. It will clarify concepts like hashing, collisions, and why the complexity is O(1) on average but can be O(n) in the worst case.

Watch from the beginning to 08:44. Pay attention to: The goal of O(1) complexity. How a hash function converts an item into an index. How collisions (two items hashing to the same index) are handled with chaining. The difference between the fast average case and the rare, slow worst case.

A key requirement for an object to be stored in a hash set is that it must be hashable, meaning its value cannot change. This is why you can add an integer, string, or tuple to a Python set, but you cannot add a list or dictionary, as they are mutable.


2. Application: The "Contains Duplicate" Problem

Now, let's apply this knowledge to a classic interview question that perfectly illustrates the power of hash sets: Contains Duplicate (LeetCode 217).

Problem Statement: Given an integer array nums, return true if any value appears at least twice in the array, and false if every element is distinct.

Example 1:

  • Input: nums = [1, 2, 3, 1]
  • Output: true (because 1 appears twice)

Example 2:

  • Input: nums = [1, 2, 3, 4]
  • Output: false

From Brute-Force to Optimal

As always, let's consider the straightforward approaches first to understand the tradeoffs.

Contains Duplicate - Leetcode 217 - Python

The video 'Contains Duplicate' from NeetCode provides a complete walkthrough of the problem, starting with the less efficient solutions and building up to the optimal hash set approach.

Watch from the beginning to 03:17. This part covers the brute-force (O(n²)) and sorting (O(n log n)) approaches. Understanding these is crucial for justifying why the hash set method is superior in an interview.

The brute-force and sorting solutions are valid, but we can achieve a linear time solution by using a hash set to keep track of the numbers we've seen.

The algorithm is simple:

  1. Initialize an empty hash set, let's call it seen.
  2. Iterate through each number n in the input array nums.
  3. For each n, check if n is already in seen.
    • If it is, we've found a duplicate. Return true.
    • If it's not, add n to seen to mark that we've now encountered it.
  4. If we finish the loop without finding any duplicates, it means all elements were unique. Return false.

Contains Duplicate - Leetcode 217 - Python

Now, let's watch the optimal solution.

Continue watching from 03:17 to the end (06:17). Focus on how the hash set is used to track 'seen' elements, enabling an O(1) check for duplicates at each step. Note the clean Python implementation.

Here's the Python code for this approach:

def contains_duplicate(nums: list[int]) -> bool:
    seen = set()  # Initialize an empty hash set
    for n in nums:
        if n in seen:
            return True
        seen.add(n)
    return False
  • Time Complexity: . We iterate through the array once. Each add and in operation on the set is on average.
  • Space Complexity: . In the worst case (no duplicates), the set will grow to store all n elements from the input array.

This is another classic time-space tradeoff. We use extra memory to achieve a faster, linear-time algorithm.

Test your understanding!

Consider the input nums = [1, 5, 2, 3, 5, 6]. Trace the hash set algorithm. What is the state of the seen set just before the function returns true?

Show answer
  1. n = 1. 1 is not in seen. Add it. seen = {1}.
  2. n = 5. 5 is not in seen. Add it. seen = {1, 5}.
  3. n = 2. 2 is not in seen. Add it. seen = {1, 5, 2}.
  4. n = 3. 3 is not in seen. Add it. seen = {1, 5, 2, 3}.
  5. n = 5. 5 is in seen. A duplicate is found. The function returns true.

The state of the seen set just before returning is {1, 5, 2, 3}.

The "Pythonic" Shortcut

Python's set type is so convenient that it allows for an elegant one-liner to solve this problem. Since a set can only contain unique elements, if you convert a list to a set, any duplicates are automatically removed.

217. Contains Duplicate - Solution & Explanation

The NeetCode website provides a text summary of this logic and also introduces a very clever and concise one-liner solution in Python that you should know.

Read the sections '3. Hash Set' and '4. Hash Set Length'. The second one explains the logic behind comparing the length of the original list to the length of a set created from it.

The logic is: if the length of the set is less than the length of the original list, it must be because duplicates were removed.

def contains_duplicate_oneliner(nums: list[int]) -> bool:
    return len(set(nums)) < len(nums)

This single line of code also has time complexity (to build the set) and space complexity (to store the set).


3. Performance in Context

The reason we choose a hash set for duplicate detection and membership testing is its unmatched average-case performance. Other data structures could do the job, but less efficiently. For example, a sorted data structure (like a balanced binary search tree, which backs Java's TreeSet) could also check for membership, but it would take time.

Comparison of Set Implementations and Their Time Complexities
This table compares different Set implementations. Notice how `HashSet` provides O(1) average time for `Add` and `Contains` operations, making it the fastest choice for simple membership testing. In contrast, a `TreeSet` (not native to Python but conceptually similar to a sorted list) offers O(log n) complexity because it maintains elements in a sorted order—a feature that costs performance.

When an interview problem asks you to check for duplicates or manage a collection of unique items without needing them to be ordered, your first thought should be a hash set.


Conclusion

You have now added another essential hash-based pattern to your toolkit. While hash maps are for associating keys with values, hash sets are for managing collections of unique items.

Key Takeaways:

  • Hash Sets Store Unique Items: They are designed to enforce uniqueness and provide extremely fast operations.
  • O(1) Operations: Adding, removing, and checking for an item in a hash set are all on average.
  • Ideal for Duplicate Detection: The "Contains Duplicate" problem is the canonical example of using a hash set to track seen elements.
  • Time-Space Tradeoff: The optimal time solution requires space, which is a common and acceptable tradeoff in interviews.

In our next lesson, we will continue exploring problems that can be solved with hashing. We'll focus on anagram and character-based problems, where you'll use hash maps to efficiently count character frequencies, combining ideas from our last two lessons.