I would like to refine @AryanFirouzian's solution and to return all duplicates by using yield return
. Also, using a temp variable simplifies the code.
public static IEnumerable<int> FindDuplicates(int[] A)
{
for (int i = 0; i < A.Length; i++) {
int absAi = Math.Abs(A[i]);
if (A[absAi] < 0) {
yield return absAi;
} else {
A[absAi] *= -1;
}
}
}
However, this solution does not return the element with the lower index and if there are more than 2 identical copies then it will return the same value more than once. Another issue is that 0 cannot be made negative.
A better solution eliminates repeated results, but still returns the second index and has a problem with 0 values. It also returns the index itself to demonstrate the wrong index problem
public static IEnumerable<(int index, int value)> FindDuplicates(int[] A)
{
for (int i = 0; i < A.Length; i++) {
int x = A[i] % A.Length;
if (A[x] / A.Length == 1) {
yield return (i, x);
}
A[x] += A.Length;
}
}
Tested with
var A = new int[] { 3, 4, 2, 5, 2, 3, 3 };
foreach (var item in FindDuplicates(A)) {
Console.WriteLine($"[{item.index}] = {item.value}");
}
It returns
[4] = 2
[5] = 3
My final solution which eliminates all these problems (at least I hope so): It encodes the first index itself by adding (i + 1) * A.Length
to the first occurrence of a value. (i + 1)
because i
can be 0
. The index can then be decoded with the reverse operation (A[x] / A.Length) - 1
.
Then, because we want to return a result only on the first repeating value, we set the value to a negative value to exclude it from further processing. Subsequently, the original value can be retrieved with Math.Abs(A[i]) % A.Length
.
public static IEnumerable<(int index, int value)> FindDuplicates(int[] A)
{
for (int i = 0; i < A.Length; i++) {
int x = Math.Abs(A[i]) % A.Length;
if (A[x] >= 0) {
if (A[x] < A.Length) { // First occurrence.
A[x] += (i + 1) * A.Length; // Encode the first index.
} else { // Second occurrence.
int firstIndex = (A[x] / A.Length) - 1; // Decode the first index.
yield return (firstIndex, x);
// Mark the value as handeled by making it negative;
A[x] *= -1; // A[x] is always >= A.Length, so no zero problem.
}
}
}
}
Returns the expected result
[2] = 2
[0] = 3
Our elements are ints that don't have an identity. I.e. we can return one of the duplicates at any index since two equal ints cannot be distinguished. In case the elements have an identity (they could be reference types with equal values but differing references or have additional fields not involved in equality testing), we would have to return the first occurrence with
yield return (firstIndex, Math.Abs(A[firstIndex]) % A.Length);
to satisfy all the requirements.
HashSet
(but that is using an extra variable). WhenAdd
returnsfalse
you got your answer. Whether it is efficient enough - not sure without seeing the sample inputs and the benchmarking code. – FranO(n^2)
algorithm which consumes the size of the array + the size of an int. – Fagaceous2
is both of them in this example). – Stalinist