Problem overview
The function removeDuplicates(nums []int) []int receives a slice of integers and must return a new slice that holds each distinct value once. The order of the result does not matter to the problem. For [2, 3, 1, 1, 4, 3, -2, 1] the answer holds 2, 3, 1, 4, -2.
The lesson asks for O(n^2) time and O(n) extra space. Faster solutions based on a hash set come in later lessons.
Approach: keep a list of what has been seen
Walk through nums from left to right and build a second slice, uniqueValues. For each value, ask whether it is already in uniqueValues. If not, this is its first occurrence, so append it. If so, skip it.
The membership test scans uniqueValues element by element. In the worst case, when all n values are different, the list grows to length n and the k-th test scans k elements, so the total work is about n^2 / 2, which is O(n^2). The result slice can hold up to n values, so the extra space is O(n).
A side effect of this approach is that the values come out in the order of their first appearance. The input above gives [2 3 1 4 -2].
Step by step
- Declare
uniqueValueswith:=as[]int{}, an empty slice. It will hold each value the first time it is met, and it is the answer at the end. - Loop with
range. Only the values matter, so writefor _, value := range nums.rangegives an index and a value on each pass, and_discards the index. Go rejects unused variables, so naming the index without using it would not compile. - Test and append. The environment provides a helper,
containsInt(uniqueValues, value), that returns true when the value is in the slice. If it returns false, append:uniqueValues = append(uniqueValues, value). The result ofappendmust be assigned back, becauseappendmay allocate a new, larger backing array and return a slice that points to it. - Return
uniqueValuesafter the loop.
The solution in Go
func removeDuplicates(nums []int) []int {
uniqueValues := []int{}
for _, value := range nums {
if !containsInt(uniqueValues, value) {
uniqueValues = append(uniqueValues, value)
}
}
return uniqueValues
}
Go details:
containsIntis not part of the standard library; the lesson supplies it. It is a linear search, equivalent to a loop that returns true on the first equal element. Since Go 1.21 the standardslices.Containsdoes the same job. Either way each call costs time proportional to the length of the slice, which is where theO(n^2)comes from.- Empty versus nil.
[]int{}is an empty, non-nil slice. For an empty input the function returns it, with length 0. Avar uniqueValues []intwould work equally well withappend, but would be nil for an empty input, which prints the same. rangecopies the value.valueis a copy of each element, which is fine for integers.- Input untouched. The function builds a new slice, so
numsis not modified. !negates a boolean. Go has nonotkeyword.
Complexity
Time: O(n^2). The loop runs n times, and each iteration scans uniqueValues, which can hold up to n values. Appending is amortized constant time.
Space: O(n). uniqueValues can hold every input value if none repeat.
Edge cases and common mistakes
- Empty input. The loop does not run and the function returns an empty slice.
- All values equal.
[5, 5, 5]gives[5]. - Forgetting to assign the result of
append. Writingappend(uniqueValues, value)as a statement is a compile error, because the new slice would be thrown away. Always writeuniqueValues = append(...). - Checking against
numsinstead ofuniqueValues. Every value is innums, so nothing would ever be appended. - Negative numbers and zero. They are ordinary values, and
-2in the example is kept once. - Using a map without knowing the order. A map-based version is faster, but ranging over a map gives random order, so the output order would vary. That is allowed here, since order is free, but it would differ from this version's first-appearance order.
Testing
[2, 3, 1, 1, 4, 3, -2, 1]gives[2 3 1 4 -2].[5, 5, 5]gives[5].- An empty slice gives an empty slice.