Problem overview
The method longestSubarray receives an Array[Int] called nums and an Int called K. It returns the length of the longest subarray, a run of consecutive elements, that contains at most K different values. Repeats of a value inside the subarray don't count extra.
For Array(1, 2, 1, 2, 3) and K = 2 the answer is 4. The first four elements, 1, 2, 1, 2, contain only the two distinct values 1 and 2. Adding the fifth element brings in 3, a third distinct value, so no longer valid subarray exists.
The lesson asks for O(n²) time and O(n) extra space. A faster sliding-window solution is the subject of a later lesson.
Approach: extend from every start until there are too many values
Try every index i as the start of a subarray and extend the end index j one step at a time. A hash set holds the distinct values of nums(i..j), and its size is the number of distinct values. After adding nums(j):
- if the set has more than
Kvalues, stop: extending further can only keep or increase the number of distinct values, so no longer subarray from this start is valid; - otherwise
nums(i..j)is valid, so compare its lengthj - i + 1with the best so far and move on toj + 1.
There are n starts, and each scans at most n elements with constant-time set operations on average, which gives O(n²). The set is created inside the outer loop, so every start begins with no values.
On the example with K = 2, start 0 collects 1, then 2, then 1 and 2 again, and the set still holds two values, so the length reaches 4. The fifth element adds 3, the set grows to three, and the scan stops. Start 1 reaches 2, 1, 2 for length 3 before 3 breaks it. The overall maximum is 4.
Step by step
- Declare
answerwithvar, set to 0. It is the best length found so far and changes later. - Loop over the start index with
for (i <- nums.indices).nums.indicesruns from 0 tonums.length - 1. - Create the set for this start. Inside the loop,
val distinctValuesis an emptymutable.HashSet[Int](). It is avalbecause the name never changes, even though the set grows. - Set up a
whileloop with a flag. Scala has nobreak, so declarevar j = iandvar stop = false, and loop whilej < nums.length && !stop. - Add the next element.
distinctValues.add(nums(j)). A set ignores a value it already holds, so the size counts distinct values only. - Check the limit. If
distinctValues.size > K, setstop = true. - Otherwise count it. In the
else,answer = math.max(answer, j - i + 1)and thenj += 1to try a longer subarray.jadvances only here, because oncestopis set the loop ends beforejis used again. - Return
answeron its own line after the outer loop. The last expression of a method is its value.
The solution in Scala
class Solution {
def longestSubarray(nums: Array[Int], K: Int): Int = {
var answer = 0
for (i <- nums.indices) {
val distinctValues = mutable.HashSet[Int]()
var j = i
var stop = false
while (j < nums.length && !stop) {
distinctValues.add(nums(j))
if (distinctValues.size > K) {
stop = true
} else {
answer = math.max(answer, j - i + 1)
j += 1
}
}
}
answer
}
}
The Scala details worth a closer look:
- The
mutable.prefix.mutable.HashSetnames the class inscala.collection.mutable. In your own file, that short form needsimport scala.collection.mutablefirst; otherwise the compiler reports "Not found: mutable". The defaultSetis immutable and has no in-placeadd. - Parameter name
K. It is a capital letter, which Scala allows for a parameter. The name must match exactly, because the lesson's tests call the method with it. - The flag instead of
break. Awhileloop re-evaluates its condition each time, so settingstopends it at the next check. Becausejisn't advanced in that case, the loop leaves withjpointing at the element that broke the limit. sizeon a set. The size is the number of elements currently stored, which is the count of distinct values. It is a constant-time call.math.max. It comes fromscala.mathand needs no import.
Complexity
Time: O(n²). Each of the n starts scans forward at most to the end of the array, doing constant average-time work per step. With a large K, such as when K is at least the number of distinct values, every start scans to the end, which is about n² / 2 steps in total.
Space: O(n). The set holds at most K + 1 values before the scan stops, which is never more than n. The set from the previous start is discarded.
Edge cases and common mistakes
- Empty array. The loop doesn't run and the answer is 0.
K = 0. The first element makes the set size 1, which is above 0, so the scan stops at once from every start.Array(1, 2, 3)withK = 0gives 0.- Large
K. WhenKis at least the number of distinct values, the whole array is valid.Array(1, 2, 3)withK = 5gives 3. - Reusing the set. A set declared outside the outer loop keeps values from earlier starts, so the size check would stop too early.
- Counting before checking. The answer must be updated only in the
elsebranch. Updating it after adding an element that breaks the limit would report a subarray withK + 1distinct values. - Counting elements instead of distinct values. A subarray like
1, 2, 1, 2has four elements but two distinct values. Only the distinct count is limited byK.
Testing
Array(1, 2, 1, 2, 3)withK = 2gives4.Array(1, 2, 3)withK = 0gives0.Array(1, 2, 3)withK = 5gives3.Array()withK = 2gives0.
