我想改进@AryanFirouzian 的解决方案并使用yield return 返回所有重复项。此外,使用临时变量可以简化代码。
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;
}
}
}
但是,此解决方案不会返回索引较低的元素,如果有超过 2 个相同的副本,那么它将多次返回相同的值。另一个问题是不能将 0 设为负数。
更好的解决方案消除了重复的结果,但仍然返回第二个索引并且有0值的问题。它还返回索引本身来演示错误索引问题
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;
}
}
经过测试
var A = new int[] { 3, 4, 2, 5, 2, 3, 3 };
foreach (var item in FindDuplicates(A)) {
Console.WriteLine($"[{item.index}] = {item.value}");
}
返回
[4] = 2
[5] = 3
我的最终解决方案消除了所有这些问题(至少我希望如此):它通过将(i + 1) * A.Length 添加到第一次出现的值来对第一个索引本身进行编码。 (i + 1) 因为i 可以是0。然后可以使用反向操作(A[x] / A.Length) - 1 解码索引。
然后,因为我们只想返回第一个重复值的结果,所以我们将该值设置为负值以将其排除在进一步处理之外。随后,可以使用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.
}
}
}
}
返回预期结果
[2] = 2
[0] = 3
我们的元素是没有标识的整数。 IE。我们可以在任何索引处返回其中一个重复项,因为无法区分两个相等的整数。如果元素具有标识(它们可能是具有相同值但引用不同的引用类型,或者具有不涉及相等性测试的其他字段),我们将不得不返回第一个匹配项
yield return (firstIndex, Math.Abs(A[firstIndex]) % A.Length);
满足所有要求。