Problem overview
The method RemoveDuplicates receives an int[] and must return an array in which every distinct value appears exactly once. The order of the result is free. For [2, 3, 1, 1, 4, 3, -2, 1], the statement shows [2, 3, 1, 4, -2], and any arrangement of those five values is accepted.
The statement also asks for in-place work with O(1) extra memory and O(n log n) time. So the method must not build a second collection of unique values while scanning. The only new array is the one returned at the end.
Approach: sort, then overwrite from the left
Sorting puts equal values next to each other, so a value is new exactly when it differs from the element before it. A simple version would copy each new value into a second list. That uses O(n) extra space, which the statement rules out.
The in-place version reuses the front of the input array as the output. A write position p marks where the last distinct value was stored. Whenever the scan finds a new value, it advances p and copies the value there. Because p never gets ahead of the scanning index i, the copy never overwrites an element that hasn't been read yet.
On the example, the sorted array is [-2, 1, 1, 1, 2, 3, 3, 4]. The first element stays at index 0, so p starts at 0. Scanning from index 1:
i = 1: 1 differs from -2, sopbecomes 1 andnums[1] = 1.i = 2andi = 3: 1 equals its neighbour, nothing happens.i = 4: 2 is new, sopbecomes 2 andnums[2] = 2.i = 5: 3 is new, sopbecomes 3 andnums[3] = 3.i = 6: 3 repeats, nothing happens.i = 7: 4 is new, sopbecomes 4 andnums[4] = 4.
The first p + 1 = 5 elements now hold -2, 1, 2, 3, 4, which is the answer.
Step by step
- Sort in place with
Array.Sort(nums), so equal values are adjacent. - Set
p = 0. It is the index of the last distinct value stored. The first element is always kept, so it is already in place. - Loop from
i = 1whilei < nums.Length. The first element needs no check, so the scan starts at the second. - Compare with the previous element:
nums[i] != nums[i - 1]. After sorting, this is the test for "met for the first time". - Store the new value.
nums[++p] = nums[i]movespforward first and then writes at the newp. - Return the first
p + 1elements as a new array withTake(p + 1).ToArray(). The count of distinct values isp + 1becausepis an index.
The solution in C#
class Solution {
public int[] RemoveDuplicates(int[] nums) {
Array.Sort(nums);
int p = 0;
for (int i = 1; i < nums.Length; ++i) {
if (nums[i] != nums[i - 1]) {
nums[++p] = nums[i];
}
}
return nums.Take(p + 1).ToArray();
}
}
The C# details to notice:
- Pre-increment. In
nums[++p],++pincreasespand then yields the new value. Withp++the index would be the old value, and the first distinct value would be overwritten. The++iin the loop header behaves the same asi++there, because its value isn't used. - The array has a fixed size. C# arrays can't shrink, so there is no way to "delete" the tail in place. The method returns a new array holding only the first
p + 1elements. The input array itself keeps its length, and its tail still holds leftover values. - Comparing against
nums[i - 1]is safe even though the loop writes into the same array. The write in iterationjgoes to an index of at mostj, so the element ati - 1has never been changed by the time iterationireads it (when a write lands exactly onj, it copies the element onto itself). TakeandToArraycome fromSystem.Linq.Take(n)yields the firstnelements andToArray()copies them into a new array.
Complexity
Time: O(n log n). The sort costs O(n log n). The scan visits each element once.
Space: O(1) beyond the output. The scan uses two integers and overwrites the input array. The returned array is the result itself, not working storage.
Edge cases and common mistakes
- All values equal.
[5, 5, 5]leavespat 0, so the result is[5]. - No duplicates.
[3, 1, 2]sorts to[1, 2, 3]and every element is copied onto itself. The result is[1, 2, 3]. - Forgetting to sort. Without sorting, duplicates that aren't neighbours survive:
[1, 2, 1]would stay as it is. - Returning
pinstead ofp + 1. That drops the last distinct value. - Using
p++in the store. It writes at the old index and leaves the count off by one. - An empty input. The loop doesn't run and
Take(1)of an empty array gives an empty result, so the method returns an empty array.
Testing
[2, 3, 1, 1, 4, 3, -2, 1]gives[-2, 1, 2, 3, 4], the same values as the statement's example in a different order.[7]gives[7].[5, 5, 5]gives[5].