170. Two Sum III - Data structure design 🔒
Problem Description
You need to design a data structure that can accept a stream of integers and check if any pair of integers in the structure sum up to a given target value.
The data structure should support two operations:
-
Adding numbers: You can add integers one at a time to the data structure. The same number can be added multiple times.
-
Finding a pair sum: Given a target value, you need to check if there exist any two numbers in the current data structure that add up to this target. The two numbers can be:
- Two different numbers (e.g.,
3 + 5 = 8
) - The same number used twice, but only if that number was added at least twice (e.g., if
4
was added twice, then4 + 4 = 8
is valid)
- Two different numbers (e.g.,
The implementation uses a hash table to track the count of each number. When finding a pair:
- For each number
x
in the structure, it checks ifvalue - x
also exists - If
x
andvalue - x
are different numbers, a valid pair is found - If
x
andvalue - x
are the same number, it's only valid if that number appears at least twice
For example:
- After adding
[1, 3, 5]
,find(4)
returnstrue
(1 + 3 = 4) - After adding
[3, 3]
,find(6)
returnstrue
(3 + 3 = 6) - After adding
[3]
,find(6)
returnsfalse
(only one 3 available)
Intuition
The key insight is that for any target sum, we need to find two numbers a
and b
such that a + b = target
. This means if we have a number a
, we need to check if target - a
exists in our data structure.
Instead of storing numbers in a simple list and checking all pairs (which would be inefficient), we can use a hash table to store both the numbers and their frequencies. Why frequencies? Because we need to handle the case where the same number might be used twice.
When we're looking for a pair that sums to value
, for each number x
we've stored, we calculate what its complement would be: y = value - x
. Then we check:
-
Does
y
exist in our hash table? If not,x
can't be part of a valid pair. -
If
y
exists andx ≠y
, we've found two different numbers that sum to our target - this is always valid. -
If
y
exists andx = y
(meaning we need to use the same number twice), we need to check if we have at least 2 occurrences of that number. This is why we track counts rather than just presence.
The beauty of this approach is that the add
operation becomes very simple - just increment a counter. The find
operation, while needing to iterate through all unique numbers in the worst case, can immediately check for complements using hash table lookups in O(1)
time per number.
This design choice optimizes for fast additions at the cost of slightly slower finds, which makes sense for a streaming data structure where we might add many numbers before querying for sums.
Solution Approach
The solution uses a hash table (Python's defaultdict
) to maintain a count of each number that has been added to the data structure.
Initialization:
- Create an empty hash table
cnt
that will map each number to its frequency count - Using
defaultdict(int)
ensures that accessing a non-existent key automatically initializes it to 0
Add Operation:
- When
add(number)
is called, simply increment the count for that number:self.cnt[number] += 1
- This is an
O(1)
operation since hash table insertion/update is constant time
Find Operation:
The find(value)
method iterates through each unique number in the hash table and checks if a valid pair exists:
-
For each number
x
and its countv
in the hash table:- Calculate the complement:
y = value - x
- Check if
y
exists in the hash table
- Calculate the complement:
-
If
y
exists, we need to verify the pair is valid:- Case 1: Different numbers (
x != y
): Ifx
andy
are different numbers, we can always form a valid pair, so returntrue
- Case 2: Same number (
x == y
): We need the same number twice, so check if the countv > 1
. If yes, returntrue
- Case 1: Different numbers (
-
If no valid pair is found after checking all numbers, return
false
Example walkthrough:
- After adding
[1, 3, 5]
, the hash table is{1: 1, 3: 1, 5: 1}
- Calling
find(4)
:- For
x = 1
:y = 4 - 1 = 3
, and3
exists in hash table,1 ≠3
, so returntrue
- For
- Calling
find(2)
:- For
x = 1
:y = 2 - 1 = 1
,1
exists but count is 1 (not > 1), continue - For
x = 3
:y = 2 - 3 = -1
,-1
doesn't exist, continue - For
x = 5
:y = 2 - 5 = -3
,-3
doesn't exist, continue - No valid pair found, return
false
- For
Time Complexity:
add
:O(1)
- single hash table updatefind
:O(n)
wheren
is the number of unique numbers in the hash table
Space Complexity: O(n)
where n
is the number of unique numbers stored
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the solution works.
Initial State: Empty data structure with hash table cnt = {}
Step 1: Add numbers
add(3)
→cnt = {3: 1}
add(5)
→cnt = {3: 1, 5: 1}
add(3)
→cnt = {3: 2, 5: 1}
(note: 3 now has count 2)
Step 2: Find pair for target = 8
find(8)
→ Need to find if any two numbers sum to 8- Check each number
x
in hash table:- For
x = 3
:- Complement
y = 8 - 3 = 5
- Does 5 exist? Yes! (
cnt[5] = 1
) - Are they different numbers? Yes! (3 ≠5)
- Valid pair found: 3 + 5 = 8 → Return
true
- Complement
- For
Step 3: Find pair for target = 6
find(6)
→ Need to find if any two numbers sum to 6- Check each number
x
:- For
x = 3
:- Complement
y = 6 - 3 = 3
- Does 3 exist? Yes! (
cnt[3] = 2
) - Are they the same number? Yes! (3 = 3)
- Do we have at least 2 occurrences? Yes! (
cnt[3] = 2 > 1
) - Valid pair found: 3 + 3 = 6 → Return
true
- Complement
- For
Step 4: Find pair for target = 10
find(10)
→ Need to find if any two numbers sum to 10- Check each number
x
:- For
x = 3
:- Complement
y = 10 - 3 = 7
- Does 7 exist? No! → Continue
- Complement
- For
x = 5
:- Complement
y = 10 - 5 = 5
- Does 5 exist? Yes! (
cnt[5] = 1
) - Are they the same number? Yes! (5 = 5)
- Do we have at least 2 occurrences? No! (
cnt[5] = 1
, not > 1) - Continue
- Complement
- No valid pair found → Return
false
- For
This example demonstrates all three scenarios: finding a pair with different numbers, finding a pair with the same number (when we have sufficient count), and not finding a valid pair.
Solution Implementation
1from collections import defaultdict
2
3class TwoSum:
4 """
5 A data structure that supports adding numbers and finding if two numbers sum to a target value.
6 """
7
8 def __init__(self):
9 """
10 Initialize the data structure with a hash map to store number frequencies.
11 """
12 # Dictionary to store each number and its count
13 self.count = defaultdict(int)
14
15 def add(self, number: int) -> None:
16 """
17 Add a number to the data structure.
18
19 Args:
20 number: The integer to add to the collection
21 """
22 # Increment the count for this number
23 self.count[number] += 1
24
25 def find(self, value: int) -> bool:
26 """
27 Check if there exist two numbers in the collection that sum to the given value.
28
29 Args:
30 value: The target sum to find
31
32 Returns:
33 True if two numbers exist that sum to value, False otherwise
34 """
35 # Iterate through each unique number in our collection
36 for num, frequency in self.count.items():
37 # Calculate the complement needed to reach the target value
38 complement = value - num
39
40 # Check if the complement exists in our collection
41 if complement in self.count:
42 # If num and complement are different, we found a valid pair
43 # If they're the same, we need at least 2 occurrences of that number
44 if num != complement or frequency > 1:
45 return True
46
47 return False
48
49
50# Your TwoSum object will be instantiated and called as such:
51# obj = TwoSum()
52# obj.add(number)
53# param_2 = obj.find(value)
54
1class TwoSum {
2 // HashMap to store each number and its frequency
3 private Map<Integer, Integer> frequencyMap = new HashMap<>();
4
5 /**
6 * Constructor to initialize the TwoSum data structure
7 */
8 public TwoSum() {
9 }
10
11 /**
12 * Add a number to the data structure
13 * @param number The number to be added
14 */
15 public void add(int number) {
16 // Increment the frequency count of the number
17 // If the number doesn't exist, it starts with 1
18 frequencyMap.merge(number, 1, Integer::sum);
19 }
20
21 /**
22 * Find if there exists any pair of numbers which sum is equal to the value
23 * @param value The target sum to find
24 * @return true if such pair exists, false otherwise
25 */
26 public boolean find(int value) {
27 // Iterate through each unique number in the map
28 for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
29 int currentNumber = entry.getKey();
30 int currentFrequency = entry.getValue();
31
32 // Calculate the complement needed to reach the target value
33 int complement = value - currentNumber;
34
35 // Check if the complement exists in the map
36 if (frequencyMap.containsKey(complement)) {
37 // If complement is different from current number, we found a valid pair
38 // If complement equals current number, we need at least 2 occurrences
39 if (currentNumber != complement || currentFrequency > 1) {
40 return true;
41 }
42 }
43 }
44
45 // No valid pair found
46 return false;
47 }
48}
49
50/**
51 * Your TwoSum object will be instantiated and called as such:
52 * TwoSum obj = new TwoSum();
53 * obj.add(number);
54 * boolean param_2 = obj.find(value);
55 */
56
1class TwoSum {
2public:
3 // Constructor - initializes an empty TwoSum data structure
4 TwoSum() {
5 }
6
7 // Adds a number to the internal data structure
8 // Time Complexity: O(1)
9 void add(int number) {
10 ++frequency_map[number];
11 }
12
13 // Finds if there exist two numbers in the data structure that add up to the target value
14 // Time Complexity: O(n) where n is the number of unique elements
15 bool find(int value) {
16 // Iterate through each unique number and its frequency
17 for (auto& [current_num, freq] : frequency_map) {
18 // Calculate the complement needed to reach the target value
19 // Using long to prevent integer overflow
20 long complement = static_cast<long>(value) - current_num;
21
22 // Check if complement exists in the map
23 if (frequency_map.count(complement)) {
24 // If complement equals current number, we need at least 2 occurrences
25 // Otherwise, one occurrence of each is sufficient
26 if (current_num != complement || freq > 1) {
27 return true;
28 }
29 }
30 }
31 return false;
32 }
33
34private:
35 // Hash map to store each number and its frequency count
36 unordered_map<int, int> frequency_map;
37};
38
39/**
40 * Your TwoSum object will be instantiated and called as such:
41 * TwoSum* obj = new TwoSum();
42 * obj->add(number);
43 * bool param_2 = obj->find(value);
44 */
45
1// Map to store the frequency of each number added
2const numberFrequency: Map<number, number> = new Map();
3
4/**
5 * Adds a number to the data structure
6 * @param number - The number to be added
7 */
8function add(number: number): void {
9 // Increment the frequency count for this number
10 const currentCount = numberFrequency.get(number) || 0;
11 numberFrequency.set(number, currentCount + 1);
12}
13
14/**
15 * Finds if there exists any pair of numbers which sum equals to the target value
16 * @param value - The target sum to find
17 * @returns true if a valid pair exists, false otherwise
18 */
19function find(value: number): boolean {
20 // Iterate through all unique numbers in our frequency map
21 for (const [currentNumber, frequency] of numberFrequency) {
22 // Calculate the complement needed to reach the target sum
23 const complement = value - currentNumber;
24
25 // Check if the complement exists in our map
26 if (numberFrequency.has(complement)) {
27 // If complement equals current number, we need at least 2 occurrences
28 // Otherwise, having both numbers is sufficient
29 if (currentNumber !== complement || frequency > 1) {
30 return true;
31 }
32 }
33 }
34
35 return false;
36}
37
Time and Space Complexity
Time Complexity:
add(number)
:O(1)
- Adding a number to the hash map (defaultdict) and incrementing its count is a constant time operation.find(value)
:O(n)
- Wheren
is the number of unique numbers stored. In the worst case, we iterate through all key-value pairs in the dictionary to check if a valid pair exists that sums to the target value.
Space Complexity:
O(n)
- Wheren
is the number of unique numbers added. The space is used to store the hash mapself.cnt
, which contains at mostn
unique keys with their corresponding counts.
Analysis Details:
- The
add
method simply increments the count for a given number in the dictionary, which takes constant time due to hash map operations. - The
find
method iterates through each unique numberx
in the dictionary and checks ify = value - x
exists in the dictionary. The condition(x != y or v > 1)
ensures that:- If
x ≠y
, we need both numbers to exist in the dictionary - If
x = y
, we need at least two occurrences of that number (count > 1)
- If
- The overall space is dominated by the storage of unique numbers and their counts in the hash map.
Common Pitfalls
1. Not Handling the Same Number Case Correctly
A frequent mistake is forgetting to check if we have enough occurrences when using the same number twice. For example:
Incorrect Implementation:
def find(self, value: int) -> bool:
for num in self.count:
complement = value - num
if complement in self.count: # Wrong! Doesn't check frequency
return True
return False
Problem: If we add only one 3
and call find(6)
, this would incorrectly return True
because it finds 3 + 3 = 6
, but we only have one 3
available.
Correct Solution: Always check frequency > 1
when num == complement
:
if complement in self.count: if num != complement or self.count[num] > 1: return True
2. Using a Set Instead of Counting Frequencies
Some might try to optimize space by using a set to track unique numbers:
Incorrect Implementation:
def __init__(self):
self.numbers = set()
def add(self, number: int) -> None:
self.numbers.add(number)
def find(self, value: int) -> bool:
for num in self.numbers:
if value - num in self.numbers:
return True
return False
Problem: This fails when we need the same number twice. For example, after adding [3, 3]
, calling find(6)
would return False
because the set only stores one 3
.
Correct Solution: Use a dictionary/hash map to track counts, not just presence.
3. Integer Overflow Concerns
In languages with fixed integer sizes, value - num
could potentially overflow:
Potential Issue:
complement = value - num # Could overflow in some languages
Solution: In Python this isn't an issue due to arbitrary precision integers, but in languages like Java or C++, consider using long integers or adding overflow checks:
// Java example with overflow consideration long complement = (long)value - num; if (complement > Integer.MAX_VALUE || complement < Integer.MIN_VALUE) { continue; }
4. Modifying the Collection During Iteration
While not directly applicable to the given solution, a pitfall could arise if someone tries to optimize by removing elements during the find operation:
Incorrect Approach:
def find(self, value: int) -> bool:
for num in list(self.count.keys()): # Creating a copy to avoid RuntimeError
complement = value - num
if complement in self.count:
# Don't modify self.count here!
return True
return False
Problem: The data structure should be persistent - find
should not modify the stored numbers.
Correct Solution: Never modify the collection in the find
method; it should be a read-only operation.
Which of the following is a good use case for backtracking?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Median of Data Stream Given a stream of numbers find the median number at any given time accurate to 1 decimal place Example add_number 1 add_number 2 add_number 3 get_median 2 0 add_number 4 get_median 2 5 Try it yourself Explanation Intuition Brute force way is to sort the entire list every time we get a new number This would be O N log N for each add_number operation One step up is to notice that the list is sorted before we add any new number to it So every
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!