LeetCode 1 - Two Sum
The problem gives us an integer array called nums and another integer called target. Our task is to find two different elements in the array whose sum equals target, then return their indices.
Difficulty: 🟢 Easy
Topics: Array, Hash Table
Solution
LeetCode 1, Two Sum
Problem Understanding
The problem gives us an integer array called nums and another integer called target. Our task is to find two different elements in the array whose sum equals target, then return their indices.
The important detail is that the output must contain indices, not the values themselves. For example, if the numbers 2 and 7 add up to 9, we must return [0, 1] because those are the positions of the numbers in the array.
The problem also guarantees that there is exactly one valid solution. This guarantee simplifies the implementation because we do not need to handle situations where no answer exists or where multiple answers are possible.
The constraint 2 <= nums.length <= 10^4 tells us that the array can become fairly large. A quadratic solution with nested loops would still technically run for this input size, but the follow-up question explicitly asks for something better than O(n^2) time complexity. This strongly suggests that we should use a more efficient lookup structure.
The values inside the array and the target can be negative, positive, or zero. Therefore, we cannot rely on sorting assumptions or positivity properties.
Several edge cases are important:
- The same value may appear multiple times, such as
[3,3]. - The correct pair may involve negative numbers.
- The answer may appear near the beginning or near the end of the array.
- We are not allowed to use the same element twice, meaning one index cannot be reused.
- Since exactly one solution exists, the algorithm can safely return immediately once the pair is found.
Approaches
Brute Force Approach
The most direct solution is to check every possible pair of numbers.
We can use two nested loops. The outer loop chooses the first number, and the inner loop checks every number that comes after it. For each pair, we compute the sum and compare it with target.
If the sum matches the target, we return the two indices.
This approach is correct because it exhaustively checks all valid pairs. Since every possible combination is considered, the correct answer will eventually be found.
However, this method is inefficient for large arrays. If the array has n elements, then we may need to check approximately n^2 / 2 pairs. That gives a time complexity of O(n^2).
Optimal Hash Map Approach
The key observation is that for every number x, we already know the number we need to complete the target:
$x + y = target$
Rearranging gives:
$y = target - x$$t$$a$$r$$g$
Instead of searching the entire array for y, we can store previously seen numbers in a hash map.
As we iterate through the array:
- We compute the complement
target - current_number. - We check whether that complement already exists in the hash map.
- If it does, we have found the answer immediately.
- Otherwise, we store the current number and its index for future lookups.
A hash map provides average O(1) lookup time, allowing the entire algorithm to run in linear time.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^2) |
O(1) |
Checks every possible pair using nested loops |
| Optimal | O(n) |
O(n) |
Uses a hash map for constant-time complement lookup |
Algorithm Walkthrough
- Create an empty hash map called
seen_numbers.
The hash map will store numbers as keys and their indices as values. This allows fast lookup to determine whether a needed complement has already appeared earlier in the array. 2. Iterate through the array using both index and value.
At each step, we process one number and determine whether it can form the target sum with a previously seen number. 3. Compute the complement.
For the current number num, calculate:
$complement = target - num$
This is the exact value needed to complete the pair. 4. Check whether the complement exists in the hash map.
If the complement is already stored, then we have previously encountered a number that pairs with the current number to produce the target sum. 5. Return the stored index and the current index.
Since the problem guarantees exactly one valid answer, we can immediately return the pair once found. 6. If the complement is not found, store the current number and index in the hash map.
This prepares the current value to potentially serve as the complement for a future number.
Why it works
The algorithm maintains the invariant that the hash map contains all numbers encountered before the current position, along with their indices.
When processing a number num, we compute the exact value needed to reach the target. If that value already exists in the hash map, then we know a valid pair has been found.
Because every element is processed exactly once, and because the correct solution is guaranteed to exist, the algorithm always returns the correct pair.
Python Solution
from typing import List
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
seen_numbers = {}
for index, num in enumerate(nums):
complement = target - num
if complement in seen_numbers:
return [seen_numbers[complement], index]
seen_numbers[num] = index
return []
The implementation starts by creating an empty dictionary called seen_numbers. In Python, dictionaries provide average O(1) lookup and insertion time, making them ideal for this problem.
The enumerate() function is used so we can access both the current index and value during iteration.
For each number, the code calculates the complement needed to reach the target. It then checks whether that complement has already been seen.
If the complement exists in the dictionary, the stored index and current index form the answer, so the function immediately returns them.
If the complement does not exist, the current number and index are inserted into the dictionary so future elements can use them.
The final return [] is technically unnecessary because the problem guarantees a valid answer, but including it makes the function syntactically complete and defensive.
Go Solution
func twoSum(nums []int, target int) []int {
seenNumbers := make(map[int]int)
for index, num := range nums {
complement := target - num
if previousIndex, exists := seenNumbers[complement]; exists {
return []int{previousIndex, index}
}
seenNumbers[num] = index
}
return []int{}
}
The Go implementation follows the exact same algorithmic logic as the Python version.
Go maps are used instead of Python dictionaries. The syntax:
previousIndex, exists := seenNumbers[complement]
retrieves both the stored value and a boolean indicating whether the key exists.
The solution returns a slice of integers containing the two indices.
Integer overflow is not a concern here because the constraints fit safely within Go's standard integer range on LeetCode platforms.
Worked Examples
Example 1
Input:
nums = [2,7,11,15]
target = 9
| Iteration | Current Number | Complement | Hash Map Before | Action |
|---|---|---|---|---|
| 0 | 2 | 7 | {} |
Store 2:0 |
| 1 | 7 | 2 | {2:0} |
Complement found, return [0,1] |
Output:
[0,1]
Example 2
Input:
nums = [3,2,4]
target = 6
| Iteration | Current Number | Complement | Hash Map Before | Action |
|---|---|---|---|---|
| 0 | 3 | 3 | {} |
Store 3:0 |
| 1 | 2 | 4 | {3:0} |
Store 2:1 |
| 2 | 4 | 2 | {3:0, 2:1} |
Complement found, return [1,2] |
Output:
[1,2]
Example 3
Input:
nums = [3,3]
target = 6
| Iteration | Current Number | Complement | Hash Map Before | Action |
|---|---|---|---|---|
| 0 | 3 | 3 | {} |
Store 3:0 |
| 1 | 3 | 3 | {3:0} |
Complement found, return [0,1] |
Output:
[0,1]
This example is important because it demonstrates that duplicate values are handled correctly without reusing the same element.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) |
Each element is processed once, and hash map operations are average O(1) |
| Space | O(n) |
The hash map may store up to all elements in the array |
The algorithm performs a single pass through the array. During each iteration, only constant-time operations are performed, specifically hash map lookup and insertion.
The extra memory usage comes from storing previously seen numbers in the hash map. In the worst case, the map may contain nearly every element before the answer is found.
Test Cases
solution = Solution()
# Provided examples
assert solution.twoSum([2, 7, 11, 15], 9) == [0, 1] # Basic example
assert solution.twoSum([3, 2, 4], 6) == [1, 2] # Pair appears later
assert solution.twoSum([3, 3], 6) == [0, 1] # Duplicate values
# Smallest valid input size
assert solution.twoSum([1, 2], 3) == [0, 1] # Minimum array length
# Negative numbers
assert solution.twoSum([-1, -2, -3, -4, -5], -8) == [2, 4] # Negative values
# Zero values
assert solution.twoSum([0, 4, 3, 0], 0) == [0, 3] # Two zeros
# Target formed by positive and negative number
assert solution.twoSum([-3, 4, 3, 90], 0) == [0, 2] # Mixed signs
# Large values within constraints
assert solution.twoSum([1000000000, -1000000000], 0) == [0, 1] # Extreme values
# Pair at the end
assert solution.twoSum([1, 2, 3, 4, 9], 13) == [3, 4] # Solution near end
# Repeated numbers with distinct indices
assert solution.twoSum([5, 5, 1], 10) == [0, 1] # Same value twice
| Test | Why |
|---|---|
[2,7,11,15], 9 |
Standard example from the problem |
[3,2,4], 6 |
Validates non-adjacent matching pair |
[3,3], 6 |
Ensures duplicates are handled correctly |
[1,2], 3 |
Tests minimum input size |
| Negative number case | Confirms algorithm works with negatives |
[0,4,3,0], 0 |
Ensures zero values work correctly |
| Mixed positive and negative | Verifies complement logic across signs |
| Extreme integer values | Confirms safe handling of constraint limits |
| Pair at end of array | Ensures full traversal works |
| Repeated values | Confirms distinct indices are used |
Edge Cases
Duplicate Values
An important edge case occurs when the correct answer uses the same numeric value twice, such as [3,3] with target 6.
A buggy implementation might accidentally reuse the same index twice. This solution avoids that problem because it only checks previously seen elements before inserting the current one into the hash map. As a result, the two indices are always distinct.
Negative Numbers
Some solutions incorrectly assume all numbers are positive and attempt optimizations based on sorting or early stopping.
For example:
nums = [-1, -2, -3, -4, -5]
target = -8
The hash map approach works naturally with negative numbers because it relies only on arithmetic subtraction and constant-time lookup.
Multiple Occurrences of the Same Number
Consider:
nums = [5,5,1]
target = 10
The algorithm correctly stores the first 5 in the hash map. When the second 5 is processed, the complement 5 already exists, so the correct pair [0,1] is returned.
This demonstrates why insertion order matters. We must check for the complement before inserting the current value.
Solution Near the End of the Array
Some incorrect implementations stop too early or make assumptions about where the answer appears.
For example:
nums = [1,2,3,4,9]
target = 13
The correct pair is at the end of the array. Since the algorithm processes every element sequentially until a match is found, it correctly handles this scenario.