Problem overview
The function removeDuplicates receives a slice of integers, nums, and must leave each distinct value exactly once. The work has to happen in place: no second slice, only O(1) extra memory. The order of the result doesn't matter.
For nums = [2, 3, 1, 1, 4, 3, -2, 1], the distinct values are 2, 3, 1, 4 and -2. The function returns a slice holding those five values. This lesson's approach returns them in ascending order, [-2 1 2 3 4], which is one of the allowed orders.
The required budget is O(n log n) time and O(1) extra space.
Approach: sort, then compact the unique values to the front
A map of seen values would make this easy, but it uses O(n) space. Sorting removes the need for it. Once the slice is sorted, equal values are neighbours, so a value is new exactly when it differs from the element before it.
First imagine building a second slice uniqueVals and appending each new value to it. The in-place version replaces that slice with the front of nums itself. A write position p marks where the last distinct value was stored. Every time the scan meets a new value, p moves one step and the value is copied there. The write position never overtakes the read position i, so nothing is overwritten before it has been read.
On the sorted example [-2, 1, 1, 1, 2, 3, 3, 4]:
- Index 0 (-2) stays where it is, and
pis 0. - Index 1 holds 1, which differs from -2.
pbecomes 1, andnums[1] = 1. - Indices 2 and 3 hold 1 again, so nothing happens.
- Index 4 holds 2:
pbecomes 2, andnums[2] = 2. - Index 5 holds 3:
pbecomes 3, andnums[3] = 3. - Index 6 holds 3 again, so nothing happens.
- Index 7 holds 4:
pbecomes 4, andnums[4] = 4.
The first p + 1 = 5 positions now hold -2 1 2 3 4. The slice behind them still contains leftovers, 3 3 4, which the result must not include.
Step by step
- Handle the empty slice first. The rest of the function treats the first element as always kept, and returns
nums[:p+1]at the end. With an empty slice that would ask for one element, and a slice expression can't go beyond the capacity, so the program would panic. Returningnumsright away avoids this. - Sort in place with
sort.Ints, which returns nothing. - Declare
p := 0. It is the index of the last distinct value placed so far. The first element is already in place. - Loop with
ifrom 1 whilei < len(nums), since the element at index 0 needs no check. - Compare with the previous element using
nums[i] != nums[i-1]. - On a new value, advance and copy. In Go,
p++is a statement and can't sit inside an index expression, so it goes on its own line before the assignmentnums[p] = nums[i]. - Return the first
p + 1elements withnums[:p+1]. The count is one more than the index.
The solution in Go
func removeDuplicates(nums []int) []int {
if len(nums) == 0 {
return nums
}
sort.Ints(nums)
p := 0
for i := 1; i < len(nums); i++ {
if nums[i] != nums[i-1] {
p++
nums[p] = nums[i]
}
}
return nums[:p+1]
}
Go details to notice:
- The result is a view of the same array.
nums[:p+1]has length 5 here, but its capacity is still 8, and the caller's ownnumsstill has length 8 and now reads[-2 1 2 3 4 3 3 4]. Go can't shorten a slice from inside a function that receives it by value, which is why the function returns the new slice, and why the caller must use the return value. - A
nilslice has length 0, so the guard also returns it unchanged.
Complexity
Time: O(n log n). The sort costs O(n log n), and the compaction pass is O(n).
Space: O(1) extra. The function only keeps p and i, and the sort works in place. The input is modified.
Edge cases and common mistakes
- Empty or
nilinput. The guard returns it as it is. Without the guard,[]int{}panics with "slice bounds out of range [:1] with capacity 0". - All values equal.
[7, 7, 7]never enters theif, sopstays 0 and the result is[7]. - Already unique values. Every comparison succeeds,
pends atn - 1, and each element is copied onto itself. The result has the same length as the input. - Returning
nums[:p]. This drops the last distinct value. The indexpis inclusive, so the length isp + 1. - Using the original
numsas the answer. The caller's slice still has the original length, with leftover values after the unique ones. Use the returned slice. - Forgetting to sort. Without sorting, equal values can be far apart, and the neighbour comparison no longer detects them.
Try these in the editor above: the example should return [-2 1 2 3 4], [7, 7, 7] should return [7], and an empty slice should return an empty slice.