Problem overview
The function two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> receives a vector of integers and a target. It must return the indices of two numbers that add up to the target, or [-1, -1] if there are none. At most one solution exists, and the same index can't be used twice, although two equal values at different indices are fine.
For nums = [2, 7, 11, 15] and target = 9, the values at indices 0 and 1 are 2 and 7, which add up to 9. The answer is [0, 1].
This lesson asks for O(n^2) time and O(1) extra space, which points to the simplest idea: check every pair.
Approach: try every pair of indices
Brute force examines each candidate answer. A candidate here is a pair of positions (i, j). To avoid testing the same pair twice, and to avoid pairing an element with itself, only pairs with i < j are used. The outer loop picks i from the first position onward, and the inner loop picks j from i + 1 to the end.
The first pair whose values add up to target is the answer, and the function returns at once. If both loops finish without a match, no pair works, and the function returns [-1, -1].
On [2, 7, 11, 15] with target 9, i = 0 pairs 2 with 7 first, and 2 + 7 = 9 matches immediately. On [1, 2, 3] with target 100, all three pairs fail and the answer is [-1, -1].
Step by step
- Store the length.
let n = nums.len();gives the number of elements.len()returns ausize, the type Rust uses for indexing, soncan be used as a loop bound without conversion. - Outer loop.
for i in 0..nvisits every possible first position. The range excludesn, so it covers exactly the valid indices. - Inner loop.
for j in i + 1..nvisits every later position. Starting ati + 1means each pair is tried once and an element is never paired with itself. - Check the pair.
if nums[i] + nums[j] == targetneeds no parentheses around the condition, but the braces are required. - Return the indices. Inside the
if, build the answer withvec![i as i32, j as i32]. The indices areusizebut the function returnsVec<i32>, and Rust never converts between integer types implicitly, soas i32does it explicitly. An explicitreturnis needed because the exit is from inside the loops. - Fall back. If nothing matched, the loops end and the last expression,
vec![-1, -1], becomes the result.
The solution in Rust
impl Solution {
fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
let n = nums.len();
for i in 0..n {
for j in i + 1..n {
if nums[i] + nums[j] == target {
return vec![i as i32, j as i32];
}
}
}
vec![-1, -1]
}
}
Rust details worth a closer look:
- Range precedence.
i + 1..nis read as(i + 1)..n, because the arithmetic binds tighter than the range operator. - Empty ranges are fine. When
i + 1reachesn, the inner range is empty and the loop body is skipped. No special case is needed for the last element. - Indexing.
nums[i]needs ausize. Using ani32variable as an index is a compile error, which is why the loops useusizeranges. - Ownership. The function takes the vector by value, but it only reads from it, so nothing is moved or cloned inside the loops.
- No semicolon on the last line.
vec![-1, -1]is the value of the function. Adding a semicolon would make the function return nothing and fail to compile.
Complexity
Time: O(n^2). In the worst case, when no pair matches or the match comes last, the inner loop runs n - 1 times for i = 0, n - 2 times for i = 1, and so on. That is n * (n - 1) / 2 checks, which is proportional to n^2. A match found early ends the search sooner, but the worst case is what is quoted.
Space: O(1). The function stores only n, i and j besides the input and the small result vector. Faster approaches exist that trade extra memory or sorting for speed; this lesson stays with the simple version.
Edge cases and common mistakes
- Empty or one-element input. With no elements the outer loop does not run, and with one element the inner range is empty. Both return
[-1, -1]without any out-of-bounds access. - Equal values.
[3, 3]with target 6 returns[0, 1]. Two equal numbers at different indices are allowed. - Pairing an element with itself.
[3, 4]with target 6 returns[-1, -1]. Startingjatiwould answer[0, 0]. - Negative numbers and zero. Nothing special is needed.
[-3, 4, 3, 90]with target 0 returns[0, 2]. - Order of the result. The smaller index comes first, because
i < j. For[7, 2]and target 9, the answer is[0, 1]. - Overflow. The sum
nums[i] + nums[j]is ani32addition. In a debug build, overflowing it panics. In an optimized build it wraps silently, so withnums = [2000000000, 2000000000]andtarget = -294967296the function returns[0, 1], because 4,000,000,000 wraps to -294,967,296. Widening toi64before adding avoids this, though ordinary inputs never reach it. - Forgetting the casts.
vec![i, j]has typeVec<usize>, which doesn't match the return type.
Testing
nums = [2, 7, 11, 15],target = 9gives[0, 1].nums = [3, 3],target = 6gives[0, 1].nums = [3, 4],target = 6gives[-1, -1].nums = [7, 2],target = 9gives[0, 1].nums = [],target = 6gives[-1, -1].
