Problem overview
The function remove_duplicates receives a vector of integers, nums, and returns a new vector holding each distinct value once. The result can be in any order.
For nums = [2, 3, 1, 1, 4, 3, -2, 1] the answer is [2, 3, 1, 4, -2]. The values 1 and 3 appear several times in the input, and each is kept only at its first appearance. This solution keeps the order of first appearance.
The lesson accepts O(n^2) time and O(n) extra space, and notes that faster solutions come later.
Approach: build a list of values seen so far
Walk through nums from left to right and keep a second vector, unique_values. For each value, ask: is it already in unique_values? If not, this is its first appearance, so push it. If it is, skip it. When the walk ends, unique_values holds every distinct value once, in the order each first appeared.
The membership question is answered by contains, which scans the vector from the start until it finds the value or reaches the end. For an input of n values, the scan can look at up to n elements, and it runs once per element. That is where the O(n^2) comes from. It is the simplest correct approach, and it shows what the later solutions improve: a sorted vector lets you compare neighbours, and a hash set answers membership in constant time on average.
On the example, the loop starts with an empty unique_values. The values 2, 3 and 1 are pushed. The next 1 is found and skipped. Then 4 is pushed, the second 3 is skipped, -2 is pushed, and the last 1 is skipped. The result is [2, 3, 1, 4, -2].
Step by step
- Create the result vector. Declare
unique_valuesas a mutableVec<i32>withVec::new(). It ismutbecause the loop pushes into it. - Loop over the values. The indices aren't needed, so loop over
&numswith the pattern&value. Iterating over a reference to the vector yields references, and the pattern copies each number out, sovalueis a plaini32. - Check and push. If
!unique_values.contains(&value), pushvalue. Each value is stored only the first time it appears. - Return
unique_valuesas the last expression, with no semicolon.
The solution in Rust
impl Solution {
fn remove_duplicates(nums: Vec<i32>) -> Vec<i32> {
let mut unique_values: Vec<i32> = Vec::new();
for &value in &nums {
if !unique_values.contains(&value) {
unique_values.push(value);
}
}
unique_values
}
}
The Rust details worth a closer look:
for &value in &nums. The expression&numsborrows the vector, and the loop yields&i32items. The pattern&valueon the left destructures the reference and copies the number out. This is allowed becausei32isCopy. Without the&in the pattern,valuewould be an&i32.containstakes a reference. The method looks for an item of the vector's element type behind a reference, so the call iscontains(&value). Passingvaluealone is a type error.!negates the check. The block runs when the value is not yet inunique_values.- Explicit type.
Vec::new()alone gives no hint about the element type, so the annotationVec<i32>states it. It would also be inferred from the laterpushand the return type. - Borrowing. Looping over
&numsdoesn't consumenums, andunique_valuesis a separate vector, so reading from one and pushing into the other never conflict. - The last expression. A function body's final expression, written without a semicolon, is its return value.
Complexity
Time: O(n^2). Each of the n iterations calls contains, which compares against at most the current length of unique_values. When all values are different, that length grows from 0 to n - 1, giving about n^2 / 2 comparisons in total. With many duplicates, unique_values stays short and the loop runs faster, but the worst case remains O(n^2).
Space: O(n). In the worst case, when no value repeats, unique_values holds as many elements as nums.
Edge cases and common mistakes
- Empty vector. The loop doesn't run and the function returns an empty vector.
- No duplicates. The result is a copy of the input, in the same order.
- All values equal. For
[5, 5, 5]only the first 5 is pushed, and the result is[5]. - Negative numbers. They are compared like any other value, so -2 in the example is kept once.
- Leaving out the
&incontains.unique_values.contains(value)doesn't compile, because the method expects a reference. - Forgetting
mut. Pushing into a vector declared withoutmutis a compile error. - Ending with a semicolon. Writing
unique_values;turns the last line into a statement, and the function then returns()instead of a vector, which doesn't match the return type.
Testing
nums = [2, 3, 1, 1, 4, 3, -2, 1]gives[2, 3, 1, 4, -2].nums = []gives[].nums = [5, 5, 5]gives[5].nums = [1, 2, 3]gives[1, 2, 3].
