Problem overview
The method twoSum receives an array of integers, nums, and a target. It must return the indices of two different positions whose values add up to target, or [-1, -1] when there is no such pair. There is at most one solution, and the same index can't be used twice, although two equal values at different indices are fine.
In the example, nums = [2, 7, 11, 15] and target = 9. The values at indices 0 and 1 are 2 and 7, and 2 + 7 = 9, so those are the two positions to report.
The statement asks for O(n) time and O(n) extra space. That rules out trying every pair of indices.
Approach: remember what you have seen
The brute-force solution uses two nested loops, one for each index of the pair, and costs O(n²). The faster idea comes from rewriting the condition. If nums[i] + nums[j] == target, then nums[i] == target - nums[j]. Once you fix the second element, its partner is a specific, known value.
So the loop fixes only the later index of the pair. For each element it asks one question: has the value target - nums[i] already appeared to the left? A dictionary answers that in constant time on average. The keys are the values seen so far, and each key maps to the index where that value was found. The index is the reason a plain set isn't enough: the answer needs positions, not just a yes or no.
Because the loop checks the dictionary before it stores the current element, an element can never be matched with itself. Every pair of positions has a later member, so the pair is found when the loop reaches that member.
Step by step
- Declare the dictionary.
indexOfValueis an empty[Int: Int]declared withvar, because entries are added while the loop runs. - Loop over the indices with
for i in 0..<nums.count. Eachnums[i]is treated as the second element of a possible pair. - Look up the partner. Subscripting a dictionary returns an optional,
nilwhen the key is missing.if let j = indexOfValue[target - nums[i]]runs its block only when the partner was seen, and binds its index toj. Inside, return[i, j]. - Store the current element with
indexOfValue[nums[i]] = i, after the check. Doing it afterwards is what prevents an element from pairing with itself. - Return
[-1, -1]after the loop. Reaching that line means every element was tried and none had a partner on its left.
The solution in Swift
class Solution {
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
var indexOfValue = [Int: Int]()
for i in 0..<nums.count {
if let j = indexOfValue[target - nums[i]] {
return [i, j]
}
indexOfValue[nums[i]] = i
}
return [-1, -1]
}
}
Swift details worth a closer look:
- The result order. The method returns
[i, j]: the current, later index first, then the earlier one. For the example it returns[1, 0], which names the same two positions as[0, 1]. - Optionals.
indexOfValue[key]has the typeInt?. Swift won't let you use it as anIntuntil you unwrap it, andif letdoes that safely, so a missing key can't crash the program. - Dictionary literal.
[Int: Int]()creates an empty dictionary. The same type can be writtenDictionary<Int, Int>(). - Value semantics. The dictionary is a value type, which is why it must be
var: adding an entry changes it. - Ranges.
0..<nums.countexcludes the upper bound, soistops at the last valid index. For an empty array the range is empty and the loop body never runs.
Complexity
Time: O(n). The loop runs at most n times, and each lookup and insertion into the dictionary takes constant time on average.
Space: O(n). In the worst case, when no pair exists, the dictionary stores one entry per element.
Edge cases and common mistakes
- Same value twice.
[3, 3]with target 6 returns[1, 0]. The first 3 is stored, and the second finds it. If the dictionary were filled completely before the loop, the second 3 would overwrite the first and an element could match itself. - Pairing with itself.
[3, 4]with target 6 returns[-1, -1]. The 3 is never compared with itself because it is stored only after its own lookup. - Empty array.
[]returns[-1, -1], since the loop doesn't run. - Negative values. Nothing changes: the dictionary treats
-2as any other key. - Overflow.
Intis 64 bits, so ordinary inputs are safe. But arithmetic that overflows traps at run time and crashes the program; it doesn't wrap. With values nearInt.maxorInt.min, the subtractiontarget - nums[i]can trap. - Force-unwrapping the lookup. Writing
indexOfValue[target - nums[i]]!crashes whenever the partner hasn't been seen. Useif let.
Testing
nums = [2, 7, 11, 15],target = 9gives[1, 0].nums = [3, 2, 4],target = 6gives[2, 1].nums = [3, 3],target = 6gives[1, 0].nums = [3, 4],target = 6gives[-1, -1].nums = [],target = 5gives[-1, -1].
