Problem overview
The method twoSum receives an Array[Int] called nums and an Int called target. It returns the indices of two different positions whose values add up to target, as an Array[Int]. When no such pair exists, it returns Array(-1, -1). There is at most one solution, and the same index can't be used twice, although two equal values at different indices are fine.
For nums = Array(2, 7, 11, 15) and target = 9, the values at indices 0 and 1 are 2 and 7, and 2 + 7 = 9. The expected answer is indices 0 and 1.
The lesson asks for O(n) time and O(n) extra space, which rules out checking every pair of indices.
Approach: remember what you have already seen
The brute-force solution uses two nested loops over the indices, which costs O(n²). To reach O(n) you need a single pass, so fix one index, call it i, and treat nums(i) as the second element of the pair. Its partner must equal target - nums(i), and if the pair exists, the partner sits to the left of i and has already been visited.
So the loop only has to answer one question for each element: "Have I already seen the value target - nums(i), and at which index?" A hash map answers that in constant time on average. Its keys are the values seen so far, and each key maps to the index where that value appeared. If the partner is in the map, you have both indices. If not, you record the current element and move on.
Take nums = Array(3, 9, -2, 5) with target = 3:
- At index 0 the value is 3, and the partner would be 0. The map is empty, so store 3 mapped to 0.
- At index 1 the value is 9, and the partner would be -6. It is not in the map, so store 9 mapped to 1.
- At index 2 the value is -2, and the partner would be 5. Not there yet, so store -2 mapped to 2.
- At index 3 the value is 5, and the partner is -2. The map holds -2 at index 2, so the answer is the array
Array(3, 2).
Step by step
- Create the map.
indexOfValueis ascala.collection.mutable.HashMap[Int, Int]declared withval. The variable never points to another map; only the map's contents change. - Loop over the indices with
for (i <- nums.indices). Eachnums(i)is a candidate second element of the pair. - Check for the partner with
indexOfValue.contains(target - nums(i)). If it is there, returnArray(i, indexOfValue(target - nums(i))). Calling the map like a function with a key reads the stored index. - Store the current element after the check, with
indexOfValue(nums(i)) = i. Doing this after the check means an element is never paired with itself. - Return
Array(-1, -1)after the loop. The loop only ends without returning when no pair adds up totarget.
The solution in Scala
class Solution {
def twoSum(nums: Array[Int], target: Int): Array[Int] = {
val indexOfValue = scala.collection.mutable.HashMap[Int, Int]()
for (i <- nums.indices) {
if (indexOfValue.contains(target - nums(i))) {
return Array(i, indexOfValue(target - nums(i)))
}
indexOfValue(nums(i)) = i
}
return Array(-1, -1)
}
}
The Scala details worth a closer look:
- Mutable versus immutable. The default
Mapin Scala is immutable, so it can't be updated in place. The lesson uses the class fromscala.collection.mutable, spelled out with its full package name. - Apply and update.
indexOfValue(key)calls the map'sapplymethod andindexOfValue(key) = valuecallsupdate. That is why reading and writing look like array indexing. Parentheses are used for arrays too:nums(i), notnums[i]. - Missing keys throw.
indexOfValue(5)on a map without the key 5 throws aNoSuchElementExceptionwith the message "key not found: 5". Thecontainscheck is what makes the read safe.indexOfValue.get(5)would returnNoneinstead. - The order of the result. The array is
Array(i, earlierIndex), so the current index comes first. ForArray(2, 7, 11, 15)and target 9, the code returnsArray(1, 0), the same two indices as[0, 1]in a different order. returninsidefor. Aforloop over a collection is turned into a call with a closure, so thisreturnleaves the whole method from inside that closure. Scala 3.5 accepts it but warns that such non-local returns are no longer supported and suggestsscala.util.boundary. Awhileloop avoids the issue.
Complexity
Time: O(n). The loop visits each index once. Each iteration does one contains, at most one lookup and one insertion, and each of those takes constant time on average for a hash map.
Space: O(n). In the worst case, no pair exists and the map ends up holding one entry per distinct value.
Edge cases and common mistakes
- Equal values.
Array(3, 3)with target 6 gives indices 1 and 0. When the loop reaches index 1, the map already holds 3 mapped to 0. - Pairing an element with itself.
Array(3, 4)with target 6 givesArray(-1, -1). If the code storednums(i)before the check, index 0 would find its own 3. - Duplicate values overwrite. The map keeps only the latest index of a value. That is safe here because a pair found with the earlier copy would already have been returned.
- Empty array. The loop body never runs and the method returns
Array(-1, -1). - Reading before checking. Calling
indexOfValue(target - nums(i))withoutcontainsthrows on the first element that has no partner yet. - Overflow.
Intis 32 bits and wraps silently. Intarget - nums(i)the subtraction can leave the range ofIntfor extreme inputs and wrap to a wrong value, so useLongif the inputs can be that large.
Testing
Array(2, 7, 11, 15), target 9 gives the indices 1 and 0.Array(3, 3), target 6 gives the indices 1 and 0.Array(3, 4), target 6 givesArray(-1, -1).Array(1, 2, 3), target 100 givesArray(-1, -1).Array(), target 5 givesArray(-1, -1).
