Problem overview
The method longestSubarrayWithoutReapeating receives an Array[Int] called nums and returns the length of the longest subarray in which no value appears twice. A subarray is a run of consecutive elements.
For Array(2, 5, 6, 2, 3, 1, 5, 6) the answer is 5. Both 5, 6, 2, 3, 1 (indices 1 to 5) and 6, 2, 3, 1, 5 (indices 2 to 6) have five different values. Any six-element window repeats something, so 5 is the maximum.
For this lesson the budget is O(n²) time and O(n) extra space. Faster solutions exist and come later.
Approach: grow a subarray from every start until it repeats
Fix a starting index i. Extend the subarray one element at a time to the right with an end index j. As long as nums(j) is a value not yet in the subarray, the subarray nums(i..j) is valid and its length is j - i + 1. The first time nums(j) is already in the subarray, stop: every longer subarray from the same start would contain the same repeat, so none of them can be valid.
Repeat this for every start and keep the largest length seen. There are n starts and each one scans up to n elements, so the time is O(n²).
The only question is how to know whether nums(j) is already in the subarray. Scanning the subarray each time would add another factor of n. A hash set of the values in the current subarray answers in constant time on average, which keeps the whole thing at O(n²). The set is created fresh for each start, so it always describes only the subarray beginning at i.
On the example, start i = 0 reaches 2, 5, 6 and then hits the second 2, so its best is 3. Start i = 1 reaches 5, 6, 2, 3, 1 and then hits 5, so its best is 5. Later starts do no better, and the result is 5.
Step by step
- Declare
resultwithvar, starting at 0. It holds the best length so far, and 0 is the correct answer for an empty array. - Loop over the start index with
for (i <- nums.indices).nums.indicesis the range0 until nums.length. - Create a new set per start. Inside the loop,
val uniqueNumsis an emptyscala.collection.mutable.HashSet[Int](). Creating it inside the loop gives every start an empty set. - Replace the inner
forwith awhile. Scala has nobreak, so the end index is avar j = iand a flagvar stop = falsecontrols the loop:while (j < nums.length && !stop). - Check for a repeat. If
uniqueNums.contains(nums(j)), setstop = trueand the loop ends at its next check. Otherwise take theelsebranch. - Update the best length. In the
else,nums(i..j)is valid, soresult = math.max(result, j - i + 1). - Add and advance. Still in the
else, calluniqueNums.add(nums(j))and thenj += 1. Awhileloop never movesjby itself. Addingnums(j)before the check would make every value look like a repeat of itself. - Return
result. The last expression of a Scala method is its value, so the method ends with justresult.
The solution in Scala
class Solution {
def longestSubarrayWithoutReapeating(nums: Array[Int]): Int = {
var result = 0
for (i <- nums.indices) {
val uniqueNums = scala.collection.mutable.HashSet[Int]()
var j = i
var stop = false
while (j < nums.length && !stop) {
if (uniqueNums.contains(nums(j))) {
stop = true
} else {
result = math.max(result, j - i + 1)
uniqueNums.add(nums(j))
j += 1
}
}
}
result
}
}
The Scala details worth a closer look:
- No
break. The language has nobreakstatement, so thestopflag is the usual way to leave a loop early. In the duplicate casejisn't advanced, and the flag ends the loop at the next condition check. - Why
whileand notfor. Aforover a range can't be stopped by a flag. Awhilere-evaluates its condition every time, so the flag takes effect. varversusval.result,jandstopchange, so they arevar.uniqueNumsis aval: the name never moves, while the set's contents change.math.max. This isscala.math.max, available without an import. For twoIntarguments it returns anInt.- The value of the method. A method body is a block, and a block's value is its last expression. The final line
resultis therefore the return value, with noreturnkeyword.
Complexity
Time: O(n²). The outer loop runs n times. For each start, the inner loop advances at most to the end of the array, and each step does a constant-time set operation on average. When all values are distinct, start i scans n - i elements, which sums to about n² / 2 steps.
Space: O(n). At any moment the set holds the values of one subarray, at most n of them. The old set is discarded when a new start begins.
Edge cases and common mistakes
- Empty array. The outer loop doesn't run and the result is 0.
- All values equal.
Array(7, 7, 7)gives 1: each start adds one element and then meets a repeat. - All values distinct.
Array(1, 2, 3)gives 3, the whole array. - Sharing one set across starts. If the set is declared outside the outer loop, values from earlier starts stay in it and later subarrays look like they contain repeats.
- Forgetting to advance
j. Withoutj += 1in theelsebranch, the loop re-reads the same element, finds it in the set on the next pass and stops, so lengths never exceed 1. - Length off by one. The subarray from
itojinclusive hasj - i + 1elements, notj - i. - Subarray versus subsequence. The elements must be consecutive. Skipping elements isn't allowed.
Testing
Array(2, 5, 6, 2, 3, 1, 5, 6)gives5.Array(7, 7, 7)gives1.Array(1, 2, 3)gives3.Array()gives0.
