Start learning free

LESSON 197 OF 348 · NESTED LOOPS & BRUTE FORCE

Longest Subarray Without Repeating in Kotlin: O(n^3) Brute Force

Practice O(n3) 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^3) time and use O(1) extra space.
(There exist faster solutions which we will discuss in future lessons)


STEP 1 OF 6
    Ctrl + Enter to check

    KOTLIN · NESTED LOOPS & BRUTE FORCE

    Longest Subarray Without Reapeating in Kotlin, explained

    Problem overview

    The function longestSubarrayWithoutReapeating(nums: IntArray): 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 consecutive elements, not an arbitrary selection.

    For nums = [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 distinct values, and no run of six consecutive elements avoids a repeat. The answer is only the length, so the function doesn't need to say which subarray it found.

    The lesson's budget is O(n^3) time and, in the statement, O(1) extra space. It is a deliberately slow first solution that checks everything. Faster approaches come in later lessons.

    Approach: try every start and grow the subarray

    A subarray is determined by its first index i and its last index j. Trying every pair with i <= j covers all candidates. For each pair, the question is whether nums[i..j] has a repeated value, and if not, whether its length j - i + 1 beats the best so far.

    Checking each pair from scratch is wasteful, and there is a useful shortcut. Fix i and let j grow one step at a time. Each step adds just one new element, nums[j], to the previous subarray nums[i..j-1]. The earlier subarray had no repeats, otherwise the loop would have stopped earlier, so the new one has a repeat only if nums[j] already occurs in the earlier part. When it does, every longer subarray starting at i contains that same repeat, so the inner loop can stop with break and move to the next i.

    With i = 1 in the example, the subarrays grow as [5], [5, 6], [5, 6, 2], [5, 6, 2, 3], [5, 6, 2, 3, 1] with length 5, and then the next element, 5, repeats the first one, so the loop stops. The best length from each start is 3, 5, 5, 5, 4, 3, 2 and 1 for i = 0 to 7, and the maximum is 5.

    Step by step

    1. Create the result. Declare var result = 0. It holds the longest valid length found so far, and it must be a var because it is updated. An empty array leaves it at 0, which is the right answer.
    2. Choose the start. Write for (i in nums.indices), so every index is tried as the first element.
    3. Choose the end. Inside it, write for (j in i until nums.size). The range starts at i, because a subarray with one element is valid, and ends at the last index.
    4. Look for a repeat. Save the new value in val searchValue = nums[j]. The previous subarray is nums[i..j), from i up to but not including j, which nums.copyOfRange(i, j) copies. Its end index is exclusive, so the copy doesn't contain nums[j] itself. The call any { it == searchValue } asks whether any element of that copy equals the new value. If so, break.
    5. Record the length. When no repeat was found, nums[i..j] is valid and has j - i + 1 elements. Update with result = maxOf(result, j - i + 1). No else is needed, because break skips this line.
    6. Return the result after both loops.

    The solution in Kotlin

    class Solution {
        fun longestSubarrayWithoutReapeating(nums: IntArray): Int {
            var result = 0
            for (i in nums.indices) {
                for (j in i until nums.size) {
                    val searchValue = nums[j]
                    if (nums.copyOfRange(i, j).any { it == searchValue }) {
                        break
                    }
                    result = maxOf(result, j - i + 1)
                }
            }
            return result
        }
    }
    

    The Kotlin details worth knowing:

    • copyOfRange(from, to). It returns a new IntArray with the elements from index from up to, but not including, index to. When j == i the range is empty, and the copy is an empty array, so the first element of every start passes the check. If from were greater than to it would throw an IllegalArgumentException, which can't happen here because j >= i.
    • any { ... }. It takes a lambda. it is the lambda's single parameter, one element of the array. any returns true as soon as one element satisfies the condition, and false for an empty array.
    • break. It leaves the innermost loop, here the j loop. The outer loop moves to the next i. It sits in the if body, outside the lambda.
    • maxOf(a, b). It is a standard function that returns the larger of two values.
    • The name. The lesson's function and the problem title spell "repeating" as "reapeating". Use the spelling as written, or the code won't match the tests.

    Complexity

    Time: O(n^3). There are about n^2 / 2 pairs (i, j). For each pair, copyOfRange copies up to n elements and any scans them, so each pair costs O(n). That multiplies to O(n^3). The early break makes typical inputs faster, but an array of all different values never triggers it.

    Space. This code allocates a copy in every check, so its extra space is not O(1). Each copy can hold up to n elements and becomes garbage right after the check, so at any moment the extra memory is O(n). The copy is a convenience of the lesson. Scanning the range i until j with a loop would need no copy.

    Edge cases and common mistakes

    • Empty array. The loops don't run and the result stays 0.
    • One element. [7] gives 1.
    • All equal. [1, 1, 1] gives 1: every start can use only its own element.
    • All different. [1, 2, 3, 4] gives 4, the whole array.
    • A repeat that isn't adjacent. [1, 2, 1, 2, 3] gives 3, from [1, 2, 3] at the end, not 4.
    • Subarray versus subsequence. [2, 5, 6, 2, 3, 1, 5, 6] has the distinct values 2, 5, 6, 3 and 1, but the elements must be consecutive, so the answer is 5.
    • Copying through j. Using copyOfRange(i, j + 1) would include nums[j] and always find a match with itself, giving 0 for every input.
    • Forgetting the break. The loop would keep extending a subarray that already has a repeat, and later steps could count one wrongly as valid.
    • Counting the length wrongly. The length of nums[i..j] is j - i + 1.

    Testing

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