Problem overview
The method longestSubarrayWithoutReapeating(_ nums: [Int]) receives an array of integers and returns the length of the longest subarray in which no value appears twice. A subarray is a run of neighbouring elements, with no gaps and no reordering.
For [2, 5, 6, 2, 3, 1, 5, 6] the answer is 5. Both [5, 6, 2, 3, 1] and [6, 2, 3, 1, 5] have five different values, and no run of six has that property. The method returns only the length, not the subarray.
The statement asks for O(n^3) time and O(1) extra space. That is a hint to use three nested levels of work: choose a start, choose an end, and check the run for repeats.
Approach: grow each subarray until a repeat appears
Every subarray is determined by its first index i and its last index j. A brute-force solution tries all those pairs and checks each run for repeats.
A shortcut saves work without changing the idea. For a fixed i, extend j one step at a time. The run from i to j adds only one new element, nums[j], to the previous run. If that value already occurs in the run before it, the new run has a repeat, and so does every longer run that starts at i. The inner loop can stop right there with break. Otherwise the run is valid, and its length j - i + 1 is a candidate for the answer.
Checking whether the new element occurs earlier in the run is itself a scan, with cost up to the run's length. So there are about n^2 pairs (i, j), each with a scan of up to n elements, hence O(n^3) in total. Faster solutions use a set or dictionary to avoid the scan, and the lesson leaves them for later.
Step by step
- Start the answer at 0.
var result = 0holds the best length found so far. An empty array keeps it at 0. - Choose the first index.
for i in 0..<nums.counttries every starting position. - Choose the last index.
for j in i..<nums.countstarts ati, so the one-element run is considered first. - Look for a repeat. Save
nums[j]insearchValueand take the slicenums[i..<j], the run beforej. If it containssearchValue,breakout of the inner loop. - Record the length. If there was no break, the run
nums[i...j]is valid and hasj - i + 1elements. Updateresultwithmax(result, j - i + 1). Noelseis needed, because thebreakskips this line. - Return the answer after both loops.
The solution in Swift
class Solution {
func longestSubarrayWithoutReapeating(_ nums: [Int]) -> Int {
var result = 0
for i in 0..<nums.count {
for j in i..<nums.count {
let searchValue = nums[j]
if nums[i..<j].contains(searchValue) {
break
}
result = max(result, j - i + 1)
}
}
return result
}
}
The Swift details worth knowing:
- Slices are views.
nums[i..<j]has the typeArraySlice<Int>. It shares the original array's storage rather than copying it, which is why the space staysO(1). A slice keeps the original indices:nums[1..<4]hasstartIndex1 andendIndex4. - The slice can be empty. When
j == ithe rangei..<iis empty, andcontainson it givesfalse, so the first element of each run is always accepted. An empty range is legal. Only a range whose start is above its end is an error, andjnever goes belowi. - Half-open versus closed.
i..<jleaves outj, which is exactly the "previous run". The run includingjwould bei...j. maxis a global function.max(result, j - i + 1)returns the larger of two values, which replaces anifthat updates the answer.breakleaves only the inner loop. The outer loop moves on to the next start index.
Complexity
Time: O(n^3). There are up to n^2 / 2 pairs (i, j), and the contains call scans up to j - i elements. In the worst case, an array of all-different values, no break ever fires, and the total work is on the order of n^3. With many repeats the break cuts the inner loop short, but the bound stays the same.
Space: O(1). The method keeps a few integers, and the slice does not copy the array.
Edge cases and common mistakes
- Empty array. The loops never run and the answer is 0.
- One element.
[1]gives 1. - All values equal.
[4, 4, 4]gives 1, because every run of two has a repeat. - All values different.
[1, 2, 3, 4]gives 4, the whole array. - Repeat at the end.
[1, 2, 3, 1]gives 3, and[1, 2, 1, 2]gives 2. - Subarray versus subsequence. The values must be neighbours. In
[1, 2, 1, 3], picking 1, 2 and 3 skips an element, which is not allowed. - Including
jin the slice. Withnums[i...j]the check would always findnums[j]itself, so every run would be rejected and the answer would be 0. - Using
continueinstead ofbreak. After a repeat, longer runs from the sameistill contain it. Withcontinuethey can be accepted wrongly, because the check compares the new element only with the run before it, and the old repeat is no longer compared with anything. For[1, 2, 1, 3, 2]that version returns 4 instead of 3.
Testing
[2, 5, 6, 2, 3, 1, 5, 6]gives5.[]gives0.[1]gives1.[4, 4, 4]gives1.[1, 2, 3, 4]gives4.[1, 2, 3, 1]gives3.
