Problem overview
The function remove_repeats receives a vector of integers nums, some of which may appear more than once. It returns a new vector in which each value appears once. The first copy of each value stays, later copies are dropped, and the values keep the order in which they first appear.
For [3, 1, 3, 2, 1] the answer is [3, 1, 2]. For [6, 6, 6, 6] it is [6]. For [4, 9, 2], with no repeats, nothing changes.
Approach
Read the numbers from left to right and remember which ones you have already met. A number that is new goes into the result; a number you have met is skipped. A HashSet is the right memory: asking whether it contains a value is fast, so each number is judged at once.
Why not just turn the vector into a set and back? A HashSet has no order. Collecting [3, 1, 3, 2, 1] into a set and back into a vector gives the right three values, but in an order that can differ from run to run, so the "order of first appearance" rule is lost. The set is only the memory here; the result vector keeps the order.
Step by step
- Create an empty
HashSet<i32>calledseen, declared withlet mut. - Create an empty
Vec<i32>calledresult, alsolet mut. - Loop over
&nums, left to right. Eachnumis a reference to a number. - Check
!seen.contains(num). The!flips the answer, so the condition means "not met yet". - Inside the
if, push*numontoresult. The star turns the reference into the number itself. - Also insert
*numintoseen, so the next copy is recognised. - After the loop, return
result. Returning inside the loop would give back only the first value.
The solution in Rust
impl Solution {
fn remove_repeats(nums: Vec<i32>) -> Vec<i32> {
let mut seen: HashSet<i32> = HashSet::new();
let mut result: Vec<i32> = Vec::new();
for num in &nums {
if !seen.contains(num) {
result.push(*num);
seen.insert(*num);
}
}
return result;
}
}
seen.contains(num) takes num as it is, because contains wants a reference and num already is one. push and insert take the value itself, hence *num.
Trying it
[3, 1, 3, 2, 1] -> [3, 1, 2]
[6, 6, 6, 6] -> [6]
[4, 9, 2] -> [4, 9, 2]
[-5, -5, 0, -5, 1000000000] -> [-5, 0, 1000000000]
Negative numbers and large values behave like any others. The values fit in i32, so no conversion is needed.
Why the order of the two steps matters
For each number the loop asks one question and does two things. The question comes first: "is it new?". Only then, if it is, does the number go into result and into seen. The two actions belong together: a number that is in result must also be in seen, or its next copy would be added again. Keeping them inside the same if guarantees that the two collections always agree.
The vector keeps the order and the set keeps the memory. Each does the job it is good at: a vector remembers positions, and a set answers "have I seen this?" without scanning.
Common mistakes
- Using the set as the answer. The set loses the order. Collecting
[3, 1, 3, 2, 1]into a set gives three values, but a set may list them in any order, so the result can't be trusted to match. - Forgetting to insert into
seen. Then nothing is ever recognised as a repeat, and the result equals the input. - Inserting before checking. If a value is added to
seenfirst, the check always says it was met, and nothing reaches the result. - Mixing up references.
result.push(num)would push a reference into aVec<i32>and fail to compile; use*num.
