Problem overview
The method longestSubarrayWithoutReapeating receives an array of numbers, nums, and returns the length of the longest subarray in which no value appears twice. A subarray is a run of neighbouring elements, so you can't skip over elements to avoid a repeat.
For nums = [2, 5, 6, 2, 3, 1, 5, 6] the answer is 5. The subarray [5, 6, 2, 3, 1] has five different values, and so does [6, 2, 3, 1, 5]. Any window of six elements would have to contain a repeat. The statement asks for O(n) time and O(n) extra space.
Approach: a window that only moves right
The brute-force idea tries every start index, extends the subarray until a repeat appears and keeps the best length. With a set for the duplicate check that is still O(n²) in the worst case, because every start rescans elements that earlier starts already visited.
The faster idea reuses that work. Suppose the longest repeat-free subarray starting at i ends just before index j. Then the elements from i + 1 to j - 1 are also all different, so the subarray starting at i + 1 can begin with them. The new window doesn't need to restart from scratch. Its end j stays where it is and only moves right.
A Set holds the values currently inside the window. Checking has, adding and deleting are all constant time on average, which keeps each step cheap.
Here is the idea on [2, 5, 6, 2, 3, 1, 5, 6]:
- Start
i = 0: the window grows over2, 5, 6and stops at the second2. Length 3. - Remove the first
2and move toi = 1: the window5, 6grows over2, 3, 1and stops at the second5. Length 5. - Remove
5and move toi = 2: the window6, 2, 3, 1grows over5and stops at the second6. Length 5. - The later starts can't beat 5, so the result stays 5.
Step by step
- Create the set.
uniqueNumsholds the values of the current window. It is declaredconstbecause the variable always refers to the same set, even though the contents change. The annotationSet<number>tells TypeScript what the empty set will hold. - Create
resultas aletstarting at 0. It is also the correct answer for an empty array. - Create
jbefore the loop. It is the index of the next element to try to add. Because the window's end never moves back, declaringjoutside the loop is what keeps the whole scan linear. - Loop over the start index
iwhilei < nums.length. - Grow the window in a
whileloop that runs whilejis inside the array andnums[j]is not already in the set. The order of the two conditions matters:&&stops at the first false operand, sonums[j]is never looked at oncejreaches the end. - Add the element to the set and move
jforward. - Record the length. When the growing loop stops, the window is the elements from
iup to but not includingj. Its length equals the set'ssize, soMath.maxkeeps the larger of the old result and that size. - Remove
nums[i]from the set before the next start. The window will begin one position later, whilejstays put. - Return
resultafter every start has been tried.
The solution in TypeScript
class Solution {
longestSubarrayWithoutReapeating(nums: number[]): number {
const uniqueNums: Set<number> = new Set();
let result: number = 0;
let j: number = 0;
for (let i = 0; i < nums.length; ++i) {
while (j < nums.length && !uniqueNums.has(nums[j])) {
uniqueNums.add(nums[j]);
++j;
}
result = Math.max(result, uniqueNums.size);
uniqueNums.delete(nums[i]);
}
return result;
}
}
The TypeScript details worth a closer look:
- Annotations disappear at run time.
Set<number>,: numberand the other annotations only help the compiler and your editor. The JavaScript that runs has none of them, and aSetwill accept any value that actually shows up. sizeis a property. A set'ssizehas no parentheses, unlike the methodshasandadd. WritinguniqueNums.size()is a type error, and the call would fail at run time becausesizeis a number.- A set compares values, not positions. Adding
3twice leaves one3in the set. That is why the window can never hold two equal numbers, and whydeleteremoves the only copy. constis about the variable. The binding can't be reassigned, butaddanddeletestill change what the set contains.++jandj++do the same thing as a standalone statement.
Complexity
Time: O(n). The outer loop runs n times. The inner loop doesn't restart, because j is declared outside and only increases. Across the whole run j moves at most n times, so the inner loop's total work is at most n steps. Each index enters the set once and leaves it at most once, and every set operation is constant time on average, giving O(n) in total.
Space: O(n). In the worst case, when all values are different, the set ends up holding every element.
Edge cases and common mistakes
- Empty array. The loop never runs and the method returns 0.
- All values equal.
[1, 1, 1]gives 1: each window grows by one element, stops at the next equal value, and loses that element whenimoves on. - All values different. The first window grows to the end of the array, and the result is
nums.length. Later starts only shrink it. - Resetting
jinside the loop. Settingj = ifor each start is correct but makes the methodO(n²)in the worst case, which breaks the lesson's time limit. - Forgetting
delete. The set would keep values that left the window, and the next window would stop too early. - Confusing subarray and subsequence. In
[2, 5, 6, 2, 3, 1, 5, 6]you could pick2, 5, 6, 3, 1by skipping the second2, but that isn't contiguous.
Testing
[2, 5, 6, 2, 3, 1, 5, 6]gives5.[1, 1, 1]gives1.[]gives0.