Problem overview
The method removeDuplicates receives an array of numbers, nums, and returns a new array that contains each distinct value once. The order of the result may be anything. The input [2, 3, 1, 1, 4, 3, -2, 1] gives [2, 3, 1, 4, -2], because the values 1 and 3 appear several times in the input but only once in the output.
The statement sets a budget of O(n^2) time and O(n) extra space: the answer is built in a second array, and finding out whether a value is already in it is allowed to be slow.
Approach: keep a list of what you have seen
Go through nums from left to right and keep a second array, uniqueValues. For each value, ask one question: is it already in uniqueValues? If not, this is its first appearance, so add it. If it is, skip it. When the loop finishes, uniqueValues holds every distinct value exactly once, in the order of its first appearance.
The cost is in the question. Searching an array means scanning it element by element, so the check takes time proportional to the size of uniqueValues, which can grow up to n. Doing that scan for each of the n values gives O(n^2). A later lesson replaces the array with a hash set to make the check fast; this one keeps the array, so you see why the set helps.
Step by step
- Declare
uniqueValueswithletas an empty array of typenumber[]. It will hold each distinct value the first time it is met, and it is the answer at the end. Writing the type out keeps the element type precise: without it, TypeScript has nothing to infer it from. - Walk over the values. The indexes don't matter here, so use a
for...ofloop that gives each element directly, namedvalue. - Check and add. Inside the loop, test
!uniqueValues.includes(value)and, if it holds, callpush(value). This ensures a value is stored only the first time it appears. - Return
uniqueValuesafter the loop.
The solution in TypeScript
class Solution {
removeDuplicates(nums: number[]): number[] {
let uniqueValues: number[] = [];
for (let value of nums) {
if (!uniqueValues.includes(value)) {
uniqueValues.push(value);
}
}
return uniqueValues;
}
}
Details worth knowing:
for...ofagainstfor...in.for (let value of nums)yields the elements.for (const i in nums)yields the indexes, and as strings:"0","1", and so on. Mixing them up is a common source of bugs.includesreturns a boolean. It compares with the "same value zero" rule: it matches equal numbers, and unlikeindexOfit also findsNaN. For example[NaN].includes(NaN)istrue, while[NaN].indexOf(NaN)is-1.- The input stays untouched. The method only reads
numsand returns a different array. The caller's array is as it was. - Compare numbers, not arrays.
includeson an array of arrays compares by reference, so two arrays with the same contents count as different. That is not an issue here, because the elements are plain numbers. - The annotation is for the compiler.
number[]is erased when the code is compiled. Pushing a string intouniqueValueswould be reported by the compiler, but nothing checks this at run time.
Complexity
Time: O(n^2). The loop runs n times. Each includes call scans up to all of uniqueValues, which has at most n elements. In the worst case, when all values are distinct, the scans cost 0, 1, 2, ... up to n - 1 steps, about n^2 / 2 in total. When there are many repeats, uniqueValues stays short and the work is smaller, but the worst case sets the bound.
Space: O(n). In the worst case uniqueValues holds all n values.
Edge cases and common mistakes
- Empty array. The loop doesn't run, and the method returns
[]. - All values equal.
[5, 5, 5]gives[5]. - No duplicates. The result has the same values as the input, in the same order.
- Checking the wrong array. Testing whether
numsincludesvalueis always true, since the value came fromnums. The test must be againstuniqueValues. - Forgetting the
!. Without the negation the method pushes only values that are already stored, so nothing is ever pushed and the result is empty. - Using the built-in
sort()to prepare. If you try to sort first and compare neighbours, remember thatsort()with no comparator sorts as text:[10, 9, 1]becomes[1, 10, 9].
Testing
[2, 3, 1, 1, 4, 3, -2, 1]gives[2, 3, 1, 4, -2].[5, 5, 5]gives[5].[]gives[].[1, 2, 3]gives[1, 2, 3].