The Two Sum problem involves determining two indices of numbers in an array such that these numbers sum up to a given target. Each input is guaranteed to have exactly one solution, and the same element cannot be used twice. The order of the returned indices is not important. Due to these constraints, there are several possible approaches to solving this problem with varying time and space complexities.
Given an array of integers, nums
, and an integer target
, the aim is to find and return the indices of two numbers that add up to target
. For example, given nums = [2, 7, 11, 15]
and target = 9
, the program should return indices [0, 1]
because nums[0] + nums[1] = 2 + 7 = 9
.
The most straightforward approach is to check every possible pair of numbers and see if they add up to the target. This involves two nested loops:
For each number in the array, iterate over the remaining numbers and check if the sum equals the target. The time complexity of this method is O(n²), making it inefficient for large arrays.
Example Code:
# Brute Force Python Implementation
def two_sum_bruteforce(nums, target):
n = len(nums)
for i in range(n):
for j in range(i+1, n):
if nums[i] + nums[j] == target:
return [i, j]
return []
The most optimal solution uses a hash map to store elements from the array along with their indices. As you iterate through the array, you calculate the complement (i.e., target - current_number
). If this complement exists in the hash map, then you have found the pair that sums to the target.
Time Complexity: O(n)
because each element is visited only once.
Space Complexity: O(n)
for storing the elements in the hash map.
Example Code:
def two_sum(nums, target):
numToIndex = {}
for i, num in enumerate(nums):
complement = target - num
if complement in numToIndex:
return [numToIndex[complement], i]
numToIndex[num] = i
return None # In theory, this case should never occur
This technique is effective when the array is sorted. Here, two pointers are used: one starting at the beginning (low
) and the other at the end (high
). Depending on the sum of the values at these pointers:
low
pointer is incremented to increase the sum.high
pointer is decremented to decrease the sum.
Time Complexity: O(n)
if the array is already sorted; otherwise, sorting the array will add an extra O(n log n)
factor. However, it is important to note that this method often requires additional handling to map the indices back to the original array.
Example Code:
def two_sum_sorted(nums, target):
# Make a copy along with original indices
indexed_nums = list(enumerate(nums))
# Sort based on the number values only
indexed_nums.sort(key=lambda x: x[1])
low, high = 0, len(indexed_nums) - 1
while low < high:
current_sum = indexed_nums[low][1] + indexed_nums[high][1]
if current_sum == target:
# Return the original indices
return [indexed_nums[low][0], indexed_nums[high][0]]
elif current_sum < target:
low += 1
else:
high -= 1
return []
Note: In the unsorted array case provided, sorting will change the indices. Therefore, extra care is needed to store the original indices if you decide to use this technique.
Approach | Time Complexity | Space Complexity | Use Case |
---|---|---|---|
Brute Force | \( \mathcal{O}(n^2) \) | \( \mathcal{O}(1) \) | Small arrays or educational purposes |
Hash Map | \( \mathcal{O}(n) \) | \( \mathcal{O}(n) \) | General use; unsorted arrays |
Two-Pointer | \( \mathcal{O}(n) \) (if sorted) | \( \mathcal{O}(n) \) | Sorted arrays where index mapping is maintained |
The key idea behind the hash map approach is to reduce the number of loop iterations by storing previously encountered numbers in a dictionary. For each element, its complement is calculated as target - num
. When the complement is found in the dictionary, this confirms the sum to be the target. This method leverages the average constant-time complexity (O(1)
) of dictionary lookups.
numToIndex
.complement = target - num
).Below is the Python implementation that uses the hash map approach. Notice how the code efficiently handles the requirement of not using the same element twice by ensuring that each element is stored and looked up only once:
def two_sum(nums, target):
# Create a dictionary to map numbers to their indices.
numToIndex = {}
# Iterate through the array with index.
for i, num in enumerate(nums):
# Calculate the required complement for the current number.
complement = target - num
# If the complement exists in the dictionary, it means we have found the pair.
if complement in numToIndex:
return [numToIndex[complement], i]
# Store or update the index of the current number.
numToIndex[num] = i
# If every number is processed and no suitable pair is found,
# return None (as per constraints this should not happen).
return None
This solution is optimal for scenarios where the array is unsorted, as it efficiently finds the target pair in a single pass.
While the hash map strategy is commonly taught and widely used, you may encounter situations where you are working with sorted arrays or have additional constraints that make the two-pointer approach appealing.
In the two-pointer approach, pointers are initialized at the start and end of the sorted array. The sum of the two pointed values is compared with the target:
An important nuance is that before using this approach, you must sort the array and take care to map the computed indices back to the original indices, since sorting rearranges the elements.
Given the assumption that every input array will have exactly one solution, the code does not need to handle the situation where multiple pairs sum to the target or none exists. However, if these assumptions are relaxed in other variations of the problem, additional checks must be in place.
When implementing this solution in a production environment, consider the following:
Whether you choose the hash map approach or the two-pointer technique, understanding the logic behind each method is crucial. The recommended strategy is to first consider the characteristics of the input. In unsorted arrays, the hash map approach provides an optimal solution in a single pass with a linear time complexity. For already sorted arrays, the two-pointer technique can be effective but requires careful handling of index mapping.
Comparing these methods allows you to determine the best approach based on the problem's constraints. For general use, particularly in coding interviews and competitive programming, the hash map solution is often preferred due to its clarity and efficiency. On the other hand, if you have control over the input or if it is already sorted, the two-pointer technique offers an alternative strategy.