Problem overview
The function two_sum receives a vector of integers, nums, and a target. It returns 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 valid pair, and the same index can't be used twice.
For nums = [2, 7, 11, 15] and target = 9, the values 2 and 7 at indices 0 and 1 sum to 9. This solution returns the later index first, so the result is [1, 0]: the pair of indices is what matters, and this order is what the code produces.
The lesson asks for O(n) time and O(n) extra space.
Approach: remember what you have seen
The brute force checks every pair of indices with two nested loops, which is O(n²). To reach O(n), keep a single loop and answer one question per element: has the partner of this element appeared earlier?
If the current value is num, its partner must be target - num, the complement. Searching the earlier elements for it would be another loop, but a hash map answers "is this value present?" in O(1) on average. The map goes from a value to the index where it was seen: value as key, index as the stored value. Finding the complement then also gives you its index.
Each loop iteration does two things in a fixed order: look up the complement, and only then insert the current element. Inserting afterwards means an element can never match itself, so index reuse is impossible. Two equal values still work, because the first is stored by the time the second arrives.
On nums = [3, 2, 4] and target = 6: the map starts empty. Index 0 (value 3) needs 3, which is not there, so 3 is stored. Index 1 (value 2) needs 4, not there, so 2 is stored. Index 2 (value 4) needs 2, which is stored with index 1, so the function returns [2, 1]. The 3 at index 0 never pairs with itself.
Step by step
- Import and create the map.
HashMapis not in Rust's prelude, so adduse std::collections::HashMap;above theimpl. Then declareindex_of_valueas amutmap fromi32toi32. - Loop with indices.
nums.iter().enumerate()yields pairs of an index and a reference to the value. The pattern(i, &num)copies the number out of the reference. - Look up the complement. Compute
complementastarget - numand ask the map for it.gettakes a reference to the key and returns anOptionof a reference.if let Some(&j)handles the found case and copies the stored index intoj. On a hit, return both indices, convertingifromusizewithas i32. - Record the current value after the check, with its index.
- Fall back to
[-1, -1]. The loop only finishes without returning when no element found a partner.
The solution in Rust
use std::collections::HashMap;
impl Solution {
fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
let mut index_of_value: HashMap<i32, i32> = HashMap::new();
for (i, &num) in nums.iter().enumerate() {
let complement = target - num;
if let Some(&j) = index_of_value.get(&complement) {
return vec![i as i32, j];
}
index_of_value.insert(num, i as i32);
}
vec![-1, -1]
}
}
Rust details worth a closer look:
- References in the loop.
iter()yields&i32. Writing&numin the pattern dereferences it, sonumis a plaini32. - Option patterns.
get(&complement)returnsOption<&i32>. InSome(&j), the&copies the integer out, sojis ani32and the borrow of the map ends at once. - Casts.
enumerate()counts inusize, while the function returnsVec<i32>, hencei as i32. The map storesi32indices for the same reason. Anascast truncates silently if the value doesn't fit, which cannot happen for any realistic vector length here. - Ownership.
numsis an ownedVec, but the loop only borrows it throughiter(). Keys and values arei32, which isCopy, so inserting into the map moves nothing. - Return values.
return vec![...]exits early from inside the loop. The last line,vec![-1, -1], has no semicolon, so it is the value of the function when the loop ends.
Complexity
Time: O(n). The loop runs at most n times, and each iteration does one lookup and one insertion, both O(1) on average.
Space: O(n). In the worst case, when no pair exists, the map ends up holding every element.
Edge cases and common mistakes
- Empty vector or a single element. The loop finds no partner, and the function returns
[-1, -1]. - Equal values.
[3, 3]with target 6 returns[1, 0]. The first 3 is stored before the second one looks for its complement. - Using an element twice.
[3, 4]with target 6 returns[-1, -1]. If the insertion came before the lookup, the 3 would find itself. - Duplicate values with different roles. If a value appears twice,
insertoverwrites the earlier index. That can't lose an answer here: two equal values that sum to the target would already have been matched when the second one arrived. - Overflow.
target - numis ani32subtraction. With the optimized build, a result outside thei32range wraps silently; in a debug build it panics. Fortarget = -2_000_000_000andnum = 2_000_000_000, the wrapped complement is 294967296. If the inputs can approach the limits ofi32, compute the complement ini64. - Negative numbers. They need no special handling:
[-3, 4, 3, 90]with target 0 returns[2, 0].
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 = 6gives[-1, -1].
