Start learning free

LESSON 196 OF 348 · NESTED LOOPS & BRUTE FORCE

Remove Duplicates from an Array in Kotlin: O(n^2) with a List

Practice O(n2) Read the full explanation

Given an array of integers, return a new array containing only the unique values.

The resulting array can be in any order.

Example:

Input: [2, 3, 1, 1, 4, 3, -2, 1]
Output: [2, 3, 1, 4, -2]
			

Note:

For this lesson, your algorithm should run in O(n^2) time and use O(n) extra space.
(There are faster solutions which we will discuss in future lessons)


Hints:

Let's use an extra array uniqueVals and push the unique values from nums there. We can traverse the array nums from left to right using an index i. We should push nums[i] into uniqueVals only if that value has not been seen before. How can we check this?

nums[i] has never been seen before if it doesn't occur in uniqueVals.
STEP 1 OF 4
    Ctrl + Enter to check

    KOTLIN · NESTED LOOPS & BRUTE FORCE

    Remove Duplicates From Array in Kotlin, explained

    Problem overview

    The function removeDuplicates(nums: IntArray): IntArray receives an array of integers and must return a new array that contains each distinct value once. The order of the result may be anything, but the lesson's solution keeps each value at its first position. For [2, 3, 1, 1, 4, 3, -2, 1] the result is [2, 3, 1, 4, -2].

    The statement allows O(n^2) time and O(n) extra space. Extra space is expected here, because the answer is a new array.

    Approach: keep a list of values already seen

    Walk through nums from left to right, and for each value ask a single question: has this value appeared before? If not, it goes into a separate collection of unique values. If yes, it is skipped. At the end, the collection holds each distinct value exactly once.

    The easy way to answer the question is to look through the unique values collected so far. A list can do that with contains. The search checks the elements one by one, so a single lookup costs time proportional to the size of the list, and doing it once per element of nums gives O(n^2) in total. The statement accepts this. A hash set answers the same question in constant time on average and brings the whole solution down to O(n), but this lesson stays with the list.

    A list is the right container because the result grows as values are found, and an IntArray has a fixed size that can't be known in advance.

    Take [2, 3, 1, 1, 4, 3, -2, 1]. The list grows like this: 2 is new, 3 is new, 1 is new, the second 1 is already there, 4 is new, the second 3 is already there, -2 is new, and the last 1 is already there. The final list is [2, 3, 1, 4, -2].

    Step by step

    1. Create the list. Declare val uniqueValues = mutableListOf<Int>(). It starts empty and will hold each value the first time it is met. The word val only fixes which list the name refers to. The contents of a mutable list can still change.
    2. Loop over the values. The indices aren't needed, so write for (value in nums). Each pass gives you the next element directly.
    3. Add only new values. Test !uniqueValues.contains(value), and when it is true, call uniqueValues.add(value). The ! reverses the answer, so the body runs only for values not yet stored.
    4. Convert and return. The function must return an IntArray, so finish with uniqueValues.toIntArray(), which copies the list into a primitive array.

    The solution in Kotlin

    class Solution {
        fun removeDuplicates(nums: IntArray): IntArray {
            val uniqueValues = mutableListOf<Int>()
            for (value in nums) {
                if (!uniqueValues.contains(value)) {
                    uniqueValues.add(value)
                }
            }
            return uniqueValues.toIntArray()
        }
    }
    

    The Kotlin details worth knowing:

    • MutableList versus List. listOf creates a read-only list that has no add function. With val u = listOf<Int>(), the call u.add(1) fails with unresolved reference 'add'. mutableListOf creates a list you can change.
    • val and mutation. A val can't be pointed at another list ('val' cannot be reassigned), but adding elements to the list it points to is fine.
    • IntArray versus MutableList<Int>. They are different types, and neither converts to the other automatically. Returning uniqueValues directly would not compile. toIntArray() makes the conversion.
    • Boxing. A MutableList<Int> stores boxed Integer objects, while an IntArray stores plain ints. Converting at the end is a copy, which adds O(n) work but doesn't change the overall complexity.
    • contains and in. uniqueValues.contains(value) can also be written value in uniqueValues. Both compare the elements with equals, which for numbers means equal values.
    • The input is not modified. The function builds a separate list and a separate result, and the caller's array keeps its duplicates.

    Complexity

    Time: O(n^2). The loop runs n times. Each pass calls contains, which scans a list that can hold up to n values. The worst case is an input where all values differ: the lists seen by successive calls have lengths 0, 1, 2, and so on, and the total is about n^2 / 2 comparisons. The final toIntArray() adds O(n).

    Space: O(n). The list holds up to n values when there are no duplicates, and the returned array has the same size.

    Edge cases and common mistakes

    • Empty array. The loop never runs and the function returns an empty array.
    • All values equal. [5, 5, 5] gives [5].
    • No duplicates. [1, 2, 3] comes back unchanged, as a new array.
    • Negative numbers and zero. They are ordinary values: the example contains -2, and it is kept once.
    • Forgetting the !. Without it, only values already in the list get added, and since the list starts empty, the result is always empty.
    • Returning the list. The declared return type is IntArray, so a MutableList<Int> is a type error.
    • Using listOf. A read-only list can't grow. Use mutableListOf.
    • Removing from the array being scanned. Arrays have a fixed size, so the approach of deleting duplicates in place doesn't fit; build a new collection instead.

    Testing

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