输入:给定n个元素的数组,其中包含从0到n-1的元素,这些数字中的任何一个都会出现任意次数。
目标:在O(n)中找到这些重复数字并仅使用常量存储空间。
例如,设n为7,数组为{1,2,3,1,3,0,6},答案应为1& 3。
我在这里检查了类似的问题,但答案使用了一些数据结构,如HashSet
等。
任何有效的算法?
答案 0 :(得分:162)
这是我提出的,不需要额外的符号位:
for i := 0 to n - 1
while A[A[i]] != A[i]
swap(A[i], A[A[i]])
end while
end for
for i := 0 to n - 1
if A[i] != i then
print A[i]
end if
end for
第一个循环置换数组,以便如果元素x
至少出现一次,那么其中一个条目将位于A[x]
位置。
请注意,它可能不会在第一次看起来像O(n),但它是 - 虽然它有一个嵌套循环,它仍然在O(N)
时间运行。只有i
这样A[i] != i
时才会发生交换,并且每个交换都设置至少一个元素A[i] == i
,之前不是真的while
。这意味着交换总数(以及N-1
循环体的总执行次数)最多为x
。
第二个循环打印A[x]
不等于x
的{{1}}的值 - 因为第一个循环保证如果x
至少存在一次A[x]
数组,其中一个实例将位于x
,这意味着它会打印数组中不存在的{{1}}值。
答案 1 :(得分:33)
caf's brilliant answer打印出数组k-1次出现k次的每个数字。这是有用的行为,但问题可能要求每个副本只打印一次,并且他暗示可以在不吹动线性时间/常数空间界限的情况下这样做。这可以通过用以下伪代码替换他的第二个循环来完成:
for (i = 0; i < N; ++i) {
if (A[i] != i && A[A[i]] == A[i]) {
print A[i];
A[A[i]] = i;
}
}
这利用了在第一个循环运行后,如果任何值m
出现多次,则保证其中一个外观位于正确位置的属性,即A[m]
。如果我们小心,我们可以使用该“家”位置来存储有关是否已打印任何重复的信息。
在caf的版本中,当我们浏览数组时,A[i] != i
暗示A[i]
是重复的。在我的版本中,我依赖于稍微不同的不变量:A[i] != i && A[A[i]] == A[i]
暗示A[i]
是我们之前未见过的重复的。 (如果你放弃了“我们之前没见过的”部分,可以看出其余部分是由于caf的不变量的真实性所暗示的,并保证所有副本在家中都有一些副本。)此属性保留在一开始(在caf的第一个循环完成之后),我在下面显示它在每个步骤后都保持。
在我们浏览数组时,测试A[i] != i
部分的成功意味着A[i]
可能是以前从未见过的副本。如果我们之前没有看到它,那么我们期望A[i]
的归属位置指向它自己 - 这就是if
条件的后半部分所测试的内容。如果是这种情况,我们打印它并改变原始位置以指回第一个找到的副本,创建一个两步“循环”。
要查看此操作不会改变我们的不变量,假设m = A[i]
对于满足i
的特定位置A[i] != i && A[A[i]] == A[i]
。很明显,我们所做的更改(A[A[i]] = i
)将通过导致其m
条件的后半部分失败来防止其他非本地出现的if
作为重复项输出,但当i
到达家乡m
时,它会有效吗?是的,它会,因为现在,即使在这个新的i
我们发现if
条件的前半部分A[i] != i
是真的,下半部分测试它是否指向它的位置是一个家庭的位置,发现它不是。在这种情况下,我们不再知道m
或A[m]
是否为重复值,但我们知道无论哪种方式,已经报告了,因为这些2个周期是保证不会出现在caf的第一个循环的结果中。 (请注意,如果m != A[m]
,则m
和A[m]
中只有一个出现多次,而另一个则根本不出现。)
答案 2 :(得分:22)
这是伪代码
for i <- 0 to n-1:
if (A[abs(A[i])]) >= 0 :
(A[abs(A[i])]) = -(A[abs(A[i])])
else
print i
end for
答案 3 :(得分:2)
对于相对较小的N,我们可以使用div / mod操作
n.times do |i|
e = a[i]%n
a[e] += n
end
n.times do |i|
count = a[i]/n
puts i if count > 1
end
不是C / C ++,但无论如何
答案 4 :(得分:1)
不是很漂亮,但至少很容易看到O(N)和O(1)属性。基本上我们扫描数组,对于每个数字,我们看到相应的位置是否已被标记为已经看过一次(N)或已经看过多次(N + 1)。如果它被标记为已经看过一次,我们打印它并标记已经看过多次。如果它没有被标记,我们标记它已经看过一次,我们将相应索引的原始值移动到当前位置(标记是破坏性操作)。
for (i=0; i<a.length; i++) {
value = a[i];
if (value >= N)
continue;
if (a[value] == N) {
a[value] = N+1;
print value;
} else if (a[value] < N) {
if (value > i)
a[i--] = a[value];
a[value] = N;
}
}
或者,更好(尽管有双循环,速度更快):
for (i=0; i<a.length; i++) {
value = a[i];
while (value < N) {
if (a[value] == N) {
a[value] = N+1;
print value;
value = N;
} else if (a[value] < N) {
newvalue = value > i ? a[value] : N;
a[value] = N;
value = newvalue;
}
}
}
答案 5 :(得分:1)
C中的一个解决方案是:
#include <stdio.h>
int finddup(int *arr,int len)
{
int i;
printf("Duplicate Elements ::");
for(i = 0; i < len; i++)
{
if(arr[abs(arr[i])] > 0)
arr[abs(arr[i])] = -arr[abs(arr[i])];
else if(arr[abs(arr[i])] == 0)
{
arr[abs(arr[i])] = - len ;
}
else
printf("%d ", abs(arr[i]));
}
}
int main()
{
int arr1[]={0,1,1,2,2,0,2,0,0,5};
finddup(arr1,sizeof(arr1)/sizeof(arr1[0]));
return 0;
}
是O(n)时间和O(1)空间复杂度。
答案 6 :(得分:1)
让我们假设我们将这个数组表示为单向图数据结构 - 每个数字都是一个顶点,它在数组中的索引指向另一个顶点,形成图的边缘。
为了更简单,我们有索引0到n-1和数字范围从0..n-1。 例如
0 1 2 3 4
a[3, 2, 4, 3, 1]
0(3) - &gt; 3(3)是一个循环。
答案:只需依靠索引遍历数组。如果a [x] = a [y]则它是一个循环,因此重复。跳到下一个索引并再次继续,依此类推,直到数组结束。 复杂性:O(n)时间和O(1)空间。
答案 7 :(得分:0)
一个微小的python代码,用于演示上面的caf方法:
a = [3, 1, 1, 0, 4, 4, 6]
n = len(a)
for i in range(0,n):
if a[ a[i] ] != a[i]: a[a[i]], a[i] = a[i], a[a[i]]
for i in range(0,n):
if a[i] != i: print( a[i] )
答案 8 :(得分:0)
在以下C函数中可以很容易地看到算法。检索原始数组虽然不是必需的,但可以将每个条目模数为 n 。
void print_repeats(unsigned a[], unsigned n)
{
unsigned i, _2n = 2*n;
for(i = 0; i < n; ++i) if(a[a[i] % n] < _2n) a[a[i] % n] += n;
for(i = 0; i < n; ++i) if(a[i] >= _2n) printf("%u ", i);
putchar('\n');
}
答案 9 :(得分:0)
static void findrepeat()
{
int[] arr = new int[7] {0,2,1,0,0,4,4};
for (int i = 0; i < arr.Length; i++)
{
if (i != arr[i])
{
if (arr[i] == arr[arr[i]])
{
Console.WriteLine(arr[i] + "!!!");
}
int t = arr[i];
arr[i] = arr[arr[i]];
arr[t] = t;
}
}
for (int j = 0; j < arr.Length; j++)
{
Console.Write(arr[j] + " ");
}
Console.WriteLine();
for (int j = 0; j < arr.Length; j++)
{
if (j == arr[j])
{
arr[j] = 1;
}
else
{
arr[arr[j]]++;
arr[j] = 0;
}
}
for (int j = 0; j < arr.Length; j++)
{
Console.Write(arr[j] + " ");
}
Console.WriteLine();
}
答案 10 :(得分:0)
我迅速创建了一个示例游乐场应用程序,用于查找时间复杂度为0(n)和恒定额外空间的重复项。 请检查网址Finding Duplicates
IMP 以上解决方案适用于数组包含0到n-1的元素,且其中任何数字出现多次的情况。
答案 11 :(得分:0)
在此处查看说明https://youtu.be/qJ_Y7pKP0e4
此处代码https://github.com/TechieExpress/DataStructures/blob/main/findDuplicates
代码片段:
contract SafeMoon
答案 12 :(得分:0)
我们可以通过 -
来完成 O(n) 时间和 O(1) 空间复杂度取第 i 个数组元素。
如果是负数,则为 +ve
最后,从数组索引(第 i 个元素)得到的数字乘以 -1。
如果数字为正,则返回索引。
def findDuplicate(self, arr: List[int]) -> int:
n=len(arr)
for i in range(0,n):
arr[(abs(arr[i]))-1]=arr[(abs(arr[i]))-1]*(-1)
if arr[(abs(arr[i]))-1]>0:
return abs(arr[i])
答案 13 :(得分:-1)
Dim ws As Worksheet
Set ws = ActiveSheet
With ws
.Columns("A:A").ColumnWidth = 17.86
.Columns("B:C").ColumnWidth = 19.86
.Columns("D:I").ColumnWidth = 10.86
End With
答案 14 :(得分:-2)
如果阵列不是太大,这个解决方案更简单, 它创建了另一个相同大小的数组用于滴答。
1创建与输入数组相同大小的位图/数组
int check_list[SIZE_OF_INPUT];
for(n elements in checklist)
check_list[i]=0; //initialize to zero
2扫描输入数组并在上面的数组中增加其计数
for(i=0;i<n;i++) // every element in input array
{
check_list[a[i]]++; //increment its count
}
3现在扫描check_list数组并打印副本一次或多次复制
for(i=0;i<n;i++)
{
if(check_list[i]>1) // appeared as duplicate
{
printf(" ",i);
}
}
当然,上面给出的解决方案占用的空间是两倍,但时间效率是O(2n),基本上是O(n)。