Problem overview
The function longest_subarray_without_reapeating receives a vector of integers, nums, and returns the length of the longest subarray in which no value appears twice. A subarray is a run of neighbouring elements.
In the example, nums = [2, 5, 6, 2, 3, 1, 5, 6] gives 5. Both [5, 6, 2, 3, 1] and [6, 2, 3, 1, 5] have five different values, and no run of six elements is free of repeats. An empty vector gives 0.
Approach: two indices that only move right
The brute-force way is to start at every index, extend while the values stay distinct, and take the best. That is O(n^2) in the worst case, because every start can walk far to the right.
The lesson's version avoids the repeated walking. It keeps a HashSet holding the values of the current window, and two indices:
i, the start of the window, which theforloop advances one step at a time;j, the next element to try to add, which stays where it stopped.
Suppose the window nums[i..j] has no repeats. When the start moves to i + 1, the window nums[i + 1..j] still has no repeats, because it is a part of a repeat-free window. So j doesn't need to go back. It continues from where it was, and only the one value that left, nums[i], has to be taken out of the set.
Each index is inserted into the set once, when j passes it, and removed at most once, when i passes it. Looking a value up in the set costs O(1) on average, so the whole scan is O(n).
Here is the example. For i = 0, j adds 2, 5 and 6, then stops at the second 2: the set holds 3 values. Then 2 leaves the set. For i = 1, the set is {5, 6}, so j adds 2, 3 and 1 and stops at the second 5. The set holds five values, which is the answer.
Step by step
- Create the set
unique_nums. It is declaredmutbecause inserting and removing change it. It holds the values of the current window, so the check for a repeat is a lookup. - Create
resultas ani32, starting at 0. An empty vector never changes it. - Create
jas ausize, before the loop overi. Declaring it outside is what keeps the scan linear, sincejnever restarts. - Loop over every start
i, from 0 to the end of the vector. - Extend the window. A
whileloop runs as long asjis inside the vector andnums[j]isn't in the set. The two conditions are joined with&&, which stops at the first false one, sonums[j]is never read withjout of range.containstakes a reference, hence&nums[j]. - Insert
nums[j]and movejforward. Rust has no++, so it isj += 1. - Record the length. When the loop stops, the window is
nums[i..j], and the set holds exactly its values, so the set'slen()is the window length. Keep the larger of that andresult.len()is ausize, so it is cast withas i32. - Remove
nums[i]from the set, because the next window starts one step later.jstays. - Return
resultas the final expression, with no semicolon.
The solution in Rust
use std::collections::HashSet;
impl Solution {
fn longest_subarray_without_reapeating(nums: Vec<i32>) -> i32 {
let mut unique_nums: HashSet<i32> = HashSet::new();
let mut result: i32 = 0;
let mut j: usize = 0;
for i in 0..nums.len() {
while j < nums.len() && !unique_nums.contains(&nums[j]) {
unique_nums.insert(nums[j]);
j += 1;
}
result = result.max(unique_nums.len() as i32);
unique_nums.remove(&nums[i]);
}
result
}
}
The Rust details worth a closer look:
HashSetcomes fromstd::collections. It isn't in the prelude, so theuseline is required.- References in
containsandremove. Both take a reference to the value, so the code passes&nums[j]and&nums[i].inserttakes the value itself. For ani32that is a copy, sonums[j]is still usable afterwards. - Return values of the set methods.
insertreturns abool, true if the value was new, andremovereturns true if the value was present. The code ignores both. That is allowed, because aboolisn't marked#[must_use]. - Why
removealways finds its value. At the end of iterationi,nums[i]is in the set. If the window was empty, the loop has just inserted it. If it wasn't, the window starts ati, and its first element isnums[i]. usizeandi32. Indices andlen()areusize, and the function returnsi32, so the one place they meet needsas i32. Theforrange0..nums.len()excludes the end, soistops at the last valid index.
Complexity
Time: O(n). i takes n values. j only goes forward, so the inner while loop runs at most n times in total across the whole run. Inserts, lookups and removals are O(1) on average.
Space: O(n). The set holds at most one entry per element, in the case where all values are different.
Edge cases and common mistakes
- Empty vector. The loop doesn't run and the result stays 0.
- One value repeated.
[4, 4, 4]returns 1. Each window holds a single 4, and it is removed before the next one starts. - All values different.
[1, 2, 3]returns 3. The first window covers everything, andjreaches the end. - Window that must move in the middle.
[1, 2, 1, 3, 4, 2]returns 4, from[2, 1, 3, 4]. The second 1 blocks the first window at length 2, and the second 2 blocks the next window at length 4. - Forgetting to remove
nums[i]. The set would keep a value that is no longer in the window, and later values equal to it would be treated as repeats. - Resetting
jinside the loop. The answer would stay correct only if the set were rebuilt each time, which brings backO(n^2). - Using
j - iandlen()interchangeably. They are equal here, butj - iis ausizeand needs the same cast.
Testing
nums = [2, 5, 6, 2, 3, 1, 5, 6]gives5.nums = [1, 2, 1, 3, 4, 2]gives4.nums = [4, 4, 4]gives1.nums = [1, 2, 3]gives3.nums = [7]gives1.nums = []gives0.
