1. Two Sum
Problem Description
You are given an array of integers nums
and an integer value target
. Your task is to find two numbers in the array that add up to the target
value and return their indices.
The problem guarantees that there will be exactly one valid solution - meaning there's exactly one pair of numbers that sum to the target. You cannot use the same element twice (you need two different positions in the array).
For example:
- If
nums = [2, 7, 11, 15]
andtarget = 9
- The numbers at index 0 (
2
) and index 1 (7
) add up to9
- So you would return
[0, 1]
The indices can be returned in any order - [0, 1]
and [1, 0]
are both acceptable answers.
The solution uses a hash table to efficiently find the pair. As we iterate through the array, for each number x
, we check if target - x
exists in our hash table. If it does, we've found our pair and return their indices. If not, we store the current number and its index in the hash table for future lookups.
Intuition
The naive approach would be to check every pair of numbers using nested loops, which would take O(n²)
time. But we can do better by thinking about what we're actually looking for.
When we're at a number x
in the array, we need to find if there's another number that equals target - x
. Instead of searching through the entire array each time, we can remember the numbers we've already seen.
The key insight is that for any number x
, its complement y = target - x
is the exact value we need to find. If we've seen y
before in our traversal, then x + y = target
and we have our answer.
This leads us to use a hash table (dictionary) to store numbers we've already processed along with their indices. As we traverse the array:
- For each number
x
, we calculate what we need:y = target - x
- We check if
y
exists in our hash table (meaning we've seen it before) - If yes, we found our pair! Return the stored index of
y
and the current index - If no, store
x
and its index in the hash table for future lookups
This way, we only need one pass through the array, reducing the time complexity to O(n)
while using O(n)
extra space for the hash table. We're essentially trading space for time - a common optimization technique.
Solution Approach
The solution uses a hash table to achieve linear time complexity. Here's how the implementation works step by step:
We initialize an empty dictionary d
to store the numbers we've seen along with their indices. The key will be the number itself, and the value will be its index in the array.
We iterate through the array using enumerate(nums)
to get both the index i
and the value x
at each position:
-
Calculate the complement: For the current number
x
, we calculatey = target - x
. This is the value we need to find to make the sum equal totarget
. -
Check if complement exists: We check if
y
is already in our dictionaryd
. If it exists, it means we've previously encountered a number that, when added to our current numberx
, equals the target. -
Return the result: If we find
y
in the dictionary, we immediately return[d[y], i]
, whered[y]
is the index of the complement we stored earlier, andi
is the current index. -
Store current number: If the complement is not found, we store the current number and its index in the dictionary as
d[x] = i
. This allows future iterations to find this number as a potential complement.
The algorithm guarantees we'll find the answer in a single pass because:
- When we encounter the first number of the pair, we store it
- When we encounter the second number of the pair, we'll find the first one already stored in the hash table
- The problem guarantees exactly one solution exists
Time Complexity: O(n)
- we traverse the array once
Space Complexity: O(n)
- in the worst case, we might store almost all elements in the hash table before finding the pair
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [2, 7, 11, 15]
and target = 9
.
We start with an empty hash table d = {}
.
Iteration 1 (i=0, x=2):
- Calculate complement:
y = 9 - 2 = 7
- Check if
7
exists ind
: No, it's empty - Store current number:
d = {2: 0}
- Continue to next iteration
Iteration 2 (i=1, x=7):
- Calculate complement:
y = 9 - 7 = 2
- Check if
2
exists ind
: Yes! It's at index 0 - Found our pair: Return
[d[2], 1]
=[0, 1]
The algorithm finds the answer in just 2 iterations. When we encounter 7
, we realize we've already seen its complement 2
at index 0, so we immediately return the indices [0, 1]
.
Let's consider another example to see how the hash table builds up: nums = [3, 2, 4]
and target = 6
.
Iteration 1 (i=0, x=3):
- Complement:
y = 6 - 3 = 3
- Check
d
for3
: Not found - Store:
d = {3: 0}
Iteration 2 (i=1, x=2):
- Complement:
y = 6 - 2 = 4
- Check
d
for4
: Not found - Store:
d = {3: 0, 2: 1}
Iteration 3 (i=2, x=4):
- Complement:
y = 6 - 4 = 2
- Check
d
for2
: Found at index 1! - Return
[1, 2]
Notice how we build up the hash table as we go, and the moment we find a complement that exists in our hash table, we have our answer.
Solution Implementation
1class Solution:
2 def twoSum(self, nums: List[int], target: int) -> List[int]:
3 # Dictionary to store number -> index mapping
4 num_to_index = {}
5
6 # Iterate through the array with index and value
7 for index, num in enumerate(nums):
8 # Calculate the complement needed to reach target
9 complement = target - num
10
11 # Check if complement exists in our dictionary
12 if complement in num_to_index:
13 # Found the pair! Return indices
14 return [num_to_index[complement], index]
15
16 # Store current number and its index for future lookups
17 num_to_index[num] = index
18
1class Solution {
2 public int[] twoSum(int[] nums, int target) {
3 // HashMap to store number value as key and its index as value
4 Map<Integer, Integer> numberToIndexMap = new HashMap<>();
5
6 // Iterate through the array
7 for (int i = 0; ; ++i) {
8 int currentNumber = nums[i];
9 int complement = target - currentNumber;
10
11 // Check if the complement exists in the map
12 if (numberToIndexMap.containsKey(complement)) {
13 // Found the pair that sums to target
14 // Return the indices: [index of complement, current index]
15 return new int[] {numberToIndexMap.get(complement), i};
16 }
17
18 // Store current number and its index in the map for future lookups
19 numberToIndexMap.put(currentNumber, i);
20 }
21 }
22}
23
1class Solution {
2public:
3 vector<int> twoSum(vector<int>& nums, int target) {
4 // Hash map to store value -> index mapping
5 unordered_map<int, int> valueToIndex;
6
7 // Iterate through the array
8 for (int i = 0; i < nums.size(); ++i) {
9 int currentNum = nums[i];
10 int complement = target - currentNum;
11
12 // Check if the complement exists in the hash map
13 if (valueToIndex.find(complement) != valueToIndex.end()) {
14 // Found the pair, return their indices
15 return {valueToIndex[complement], i};
16 }
17
18 // Store current number and its index in the hash map
19 valueToIndex[currentNum] = i;
20 }
21
22 // This line should never be reached if input guarantees a solution
23 return {};
24 }
25};
26
1/**
2 * Finds two indices in the array whose values sum to the target
3 * @param nums - Array of integers to search through
4 * @param target - Target sum to find
5 * @returns Array containing the two indices whose values sum to target
6 */
7function twoSum(nums: number[], target: number): number[] {
8 // HashMap to store value -> index mappings for visited elements
9 const valueToIndexMap = new Map<number, number>();
10
11 // Iterate through the array
12 for (let currentIndex = 0; ; ++currentIndex) {
13 const currentValue = nums[currentIndex];
14 const complement = target - currentValue;
15
16 // Check if complement exists in our map
17 if (valueToIndexMap.has(complement)) {
18 // Found the pair - return indices
19 return [valueToIndexMap.get(complement)!, currentIndex];
20 }
21
22 // Store current value and its index for future lookups
23 valueToIndexMap.set(currentValue, currentIndex);
24 }
25}
26
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array nums
. This is because the algorithm iterates through the array exactly once in the worst case. Each iteration performs constant-time operations: checking if a key exists in the dictionary (O(1)
average case for hash table lookup), dictionary insertion (O(1)
average case), and arithmetic operations.
The space complexity is O(n)
, where n
is the length of the array nums
. In the worst case, when no two numbers sum to the target until the last possible pair, the algorithm stores n-1
elements in the dictionary d
. The dictionary stores key-value pairs where keys are the numbers from nums
and values are their indices, requiring space proportional to the number of elements processed.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using the Same Element Twice
The Pitfall: A common mistake is checking if target - num
exists without ensuring it's at a different index. This becomes problematic when exactly half of the target appears in the array.
Example of the issue:
nums = [3, 2, 4]
,target = 6
- When we reach index 0 (value = 3), we calculate complement = 6 - 3 = 3
- If we had stored 3 in our dictionary first and then checked, we might incorrectly return
[0, 0]
Why the given solution avoids this: The solution cleverly avoids this by checking for the complement BEFORE adding the current number to the dictionary. This ensures we never match a number with itself.
2. Overwriting Dictionary Values with Duplicate Numbers
The Pitfall: When the array contains duplicate values, storing them in the dictionary might overwrite previous indices.
Example scenario:
nums = [3, 3]
,target = 6
- If we store both 3's in the dictionary, the second one overwrites the first
Why this doesn't break the solution: The algorithm finds the answer as soon as a valid pair is encountered. Since we check for complements before storing, we'll find the pair [0, 1]
when processing the second 3, before any overwriting occurs. The problem guarantees exactly one solution, so we don't need to worry about finding all pairs.
3. Returning Values Instead of Indices
The Pitfall: Accidentally returning the actual numbers that sum to target instead of their indices.
Incorrect implementation:
if complement in num_to_index: return [complement, num] # Wrong! Returns values
Correct implementation:
if complement in num_to_index: return [num_to_index[complement], index] # Returns indices
4. Pre-populating the Dictionary
The Pitfall: Some might try to optimize by first building the complete dictionary, then searching for pairs:
# Problematic approach
num_to_index = {num: i for i, num in enumerate(nums)}
for i, num in enumerate(nums):
complement = target - num
if complement in num_to_index and num_to_index[complement] != i:
return [i, num_to_index[complement]]
The issue: This approach requires extra logic to handle the same-element case and doesn't provide any performance benefit while making the code more complex.
How many times is a tree node visited in a depth first search?
Recommended Readings
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!