Start learning free

LESSON 290 OF 348 · SLIDING WINDOW

Longest Subarray Without Repeating in Kotlin: O(n) with a HashSet

Practice O(n) Read the full explanation

Given an input array of integers, find the length of the longest subarray without reapeating integers.

Example

Input: nums = [2, 5, 6, 2, 3, 1, 5, 6]
Output: 5
Explanation: [5, 6, 2, 3, 1] or [6, 2, 3, 1, 5] are both valid and of maximum length 5

Note:

For this lesson, your algorithm should run in O(n) time and use O(n) extra space


STEP 1 OF 9
    Ctrl + Enter to check

    KOTLIN · SLIDING WINDOW

    Longest Subarray Without Reapeating in Kotlin, explained

    Problem overview

    You get an IntArray called nums and must return 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 the way you can in a subsequence.

    For [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]. No run of six elements is free of repeats, because any six neighbours in this array contain some value twice.

    The lesson asks for O(n) time and O(n) extra space.

    Approach

    The brute force tries every start index, walks right until a value repeats and records the length. That costs O(n^2) in the worst case, because each start may walk far to the right again.

    The faster idea is a sliding window that never moves backwards:

    • i is the left edge of the window.
    • j is the index of the next element that might join the window on the right.
    • A HashSet holds exactly the values inside the window, so asking "is this value already in the window?" takes constant time on average.

    Suppose the window starting at i has grown as far as it can. When the left edge moves to i + 1, the elements from i + 1 up to j - 1 are still free of repeats, since they were already in the window. So j doesn't need to go back to i + 1. It continues from where it stopped. Each index enters the window once and leaves it once, which makes the total work linear.

    Step by step

    1. The set. uniqueNums is a HashSet<Int> that holds the current window's values. It is a val because the variable always points to the same set; the set's contents still change with add and remove.
    2. The best length. result starts at 0, which is also the right answer for an empty array. It is a var because it gets replaced whenever a longer window turns up.
    3. The right edge. j is declared before the loop, not inside it. If it were reset for each i, the scan would repeat work and lose the linear running time.
    4. The left edge. The for loop over nums.indices makes each index the start of a window in turn.
    5. Grow the window. The while loop continues as long as j is inside the array and nums[j] is not yet in the set. The order of the two conditions matters: with &&, Kotlin stops at the first false one, so nums[j] is never read when j equals nums.size.
    6. Take the element. Inside the loop, nums[j] is added to the set and j moves on by one.
    7. Record the length. When the loop stops, the window covers i up to but not including j, and its length is j - i, the same as uniqueNums.size. maxOf keeps the larger of the old best and this length.
    8. Slide the left edge. Before i advances, nums[i] is removed from the set, since the next window no longer contains it. Nothing else changes.
    9. Return. After every start has been tried, result holds the answer.

    The solution in Kotlin

    class Solution {
        fun longestSubarrayWithoutReapeating(nums: IntArray): Int {
            val uniqueNums = HashSet<Int>()
            var result = 0
            var j = 0
            for (i in nums.indices) {
                while (j < nums.size && nums[j] !in uniqueNums) {
                    uniqueNums.add(nums[j])
                    ++j
                }
                result = maxOf(result, uniqueNums.size)
                uniqueNums.remove(nums[i])
            }
            return result
        }
    }
    

    A few Kotlin details:

    • HashSet is available without an import. Kotlin's HashSet is the JVM's java.util.HashSet under another name.
    • x !in set is the same as !set.contains(x). The in operator calls contains.
    • The element type is Int, and the set stores boxed integers behind the scenes. For this problem the cost is irrelevant, and equal values compare as equal.
    • nums.indices is the range 0 until nums.size, so an empty array runs the loop zero times and the function returns 0.
    • ++j and j++ behave the same here because the result of the expression is not used.

    Complexity

    Time: O(n). The index j only ever increases, so the inner while loop runs at most n times in total over the whole run, not n times per iteration of the for loop. Each element is added to the set once and removed once, and each of those operations takes constant time on average.

    Space: O(n). In the worst case, when every value is different, the set holds all n values at once.

    Edge cases and common mistakes

    • Empty array. The loop never runs and result stays 0.
    • All values equal. For [7, 7, 7] every window has length 1, so the answer is 1.
    • All values different. The first window grows to the end of the array and the answer is nums.size.
    • Subarray versus subsequence. Values must be neighbours. Picking 2, 5, 6, 3, 1 out of the example by skipping the second 2 is not allowed.
    • Resetting j inside the loop. This gives correct answers but brings back the quadratic running time.
    • Forgetting to remove nums[i]. The set would keep a value that has left the window and block a legitimate extension later.
    • Swapping the while conditions. Writing nums[j] !in uniqueNums && j < nums.size reads past the end of the array on the last step and throws ArrayIndexOutOfBoundsException.

    Testing

    • [2, 5, 6, 2, 3, 1, 5, 6] returns 5.
    • [] returns 0.
    • [7, 7, 7] returns 1.
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free