Problem overview
removeDuplicates takes an IntArray named nums and returns an IntArray in which every value appears once. The order of the result doesn't matter, and the work should happen inside nums with O(1) extra memory, apart from the returned array.
For [2, 3, 1, 1, 4, 3, -2, 1], the unique values are 2, 3, 1, 4 and -2. This solution returns them in sorted order, [-2, 1, 2, 3, 4], which the problem allows.
Approach
An easy version uses a set or a second list: walk the array and keep a value only if it is new. That needs extra memory proportional to the number of unique values.
Sorting avoids it. Once the array is sorted, duplicates form runs of equal neighbours, and a value is new exactly when it differs from the element before it. We then use the front of the same array as the output. A write position p marks where the last unique value was stored. Every time a new value appears, we advance p and copy the value there. Since p never gets ahead of the element being read, we never overwrite a value we still need.
For the sorted array [-2, 1, 1, 1, 2, 3, 3, 4]: p starts at 0 (holding -2). At index 1 the value 1 is new, so p becomes 1 and nums[1] = 1. Indices 2 and 3 are repeats and are skipped. Index 4 holds 2, which is new, so p = 2 and nums[2] = 2, and so on. At the end p = 4, and the first five elements, [-2, 1, 2, 3, 4], are the answer.
Step by step
- Handle the empty array. If
nums.isEmpty(), return it. Everything after this assumes a first element exists, andcopyOfRange(0, p + 1)on an empty array would throw anIndexOutOfBoundsException. - Sort in place with
nums.sort(), so equal values become neighbours. - Create
pwithvar, starting at 0. The first element is always kept, and it already sits at index 0. - Loop over
1 until nums.size. Index 0 is already in place, so the scan starts at the second element. - Check for a new value. If
nums[i] != nums[i - 1], the value is met for the first time. - Record it. Increment
pfirst (p++on its own line), then assignnums[p] = nums[i]. The order matters:ppoints at the last stored value, so the next free slot isp + 1. - Return the unique prefix. The unique values occupy indices
0top, which isp + 1values.copyOfRange(0, p + 1)copies them into a new array, since the end index is exclusive.
The solution in Kotlin
class Solution {
fun removeDuplicates(nums: IntArray): IntArray {
if (nums.isEmpty()) {
return nums
}
nums.sort()
var p = 0
for (i in 1 until nums.size) {
if (nums[i] != nums[i - 1]) {
p++
nums[p] = nums[i]
}
}
return nums.copyOfRange(0, p + 1)
}
}
Kotlin details worth knowing:
- A Kotlin array can't shrink. That is why the function returns a new, shorter array with
copyOfRangeinstead of deleting fromnums. - After the loop, the elements past index
pinnumsstill hold leftover values. For[2, 3, 1, 1, 4, 3],numsends as[1, 2, 3, 4, 3, 4]. Only the returned copy is clean, so use that, notnums. - Comparing with
nums[i - 1]stays correct even though the loop writes into the array. A write during iterationilands at indexp, which is never greater thani, and whenpequalsiit writes the value that is already there. So the next iteration still sees the sorted original value at indexi. - Function parameters can't be reassigned, but the array they refer to can be modified. That is why in-place changes to
numscompile.
Complexity
- Time:
O(n log n)for the sort, plus oneO(n)pass and anO(n)copy. - Space:
O(1)beyond the returned array: the deduplication itself reusesnums.
The extra-memory version that collects values in a second list has the same running time but uses O(n) space in the worst case, when all values are different. The sorted, in-place version trades a little extra thinking about indices for that saved memory.
Edge cases and common mistakes
- Empty input. Covered by the first check. Without it, even the unconditional
nums.sort()is harmless, but the finalcopyOfRange(0, 1)is not. - One element. The loop doesn't run,
pis 0, and the result is a one-element copy. - All values equal.
[5, 5, 5]gives[5]. - Returning
numsitself. It still has the leftover tail, so it is longer than the answer. - Returning
pelements.copyOfRange(0, p)drops the last unique value, becausepis an index, not a count. - Forgetting the sort. Duplicates that aren't adjacent, such as the two 3s in
[3, 1, 3], would both survive. - Assigning before incrementing. Writing
nums[p] = nums[i]beforep++overwrites the last stored unique value.
Testing
[2, 3, 1, 1, 4, 3, -2, 1]returns[-2, 1, 2, 3, 4].[7]returns[7].[]returns an empty array.[3, 1, 3]returns[1, 3].
The first call returns the unique values in sorted order, not in their original order. The problem accepts any order, so that is fine, but it is worth knowing if you compare the output by eye with the example in the statement, which lists them differently.