Answers:
这是我想出的,不需要额外的符号位:
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
第一个循环对数组进行置换,因此,如果element x
至少存在一次,则这些条目之一将位于position A[x]
。
请注意,乍一看它看起来可能不是O(n),但它是-尽管它具有嵌套循环,但仍会O(N)
及时运行。仅当有一个会发生交换i
,使得A[i] != i
,每个交换台的至少一种元素,使得A[i] == i
,在那里,这不是真之前。这意味着交换的总数(以及while
循环体的执行总数)最多为N-1
。
第二环路打印的值x
对其A[x]
不等于x
-由于第一循环保证,如果x
存在至少一次阵列中,其中的一个实例将在A[x]
,这意味着它打印的那些值x
中不存在在数组。
5
它不在范围内0..N-1
(N
本例中为5
)。
print
语句来print i
轮流到这个解决stackoverflow.com/questions/5249985/...和(假设“袋”是一个可修改的阵列)的Qk的stackoverflow.com/questions/3492302/...。
caf出色的答案是将出现在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]
发生多次,而另一个根本不发生。)
这是伪代码
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
-
用~
的零问题。
O(n)
隐藏空间- n
符号位。如果将数组定义为每个元素只能包含0
和之间的值n-1
,则显然不起作用。
对于相对较小的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 ++,但无论如何
并不是很漂亮,但至少很容易看到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;
}
}
}
if (value > i) a[i--] = a[value];
:如果value <= i
那么我们已经处理过的值,a[value]
就可以安全地覆盖它。我也不会说O(N)性质很明显!清楚说明:主循环运行N
时间,加上该a[i--] = a[value];
行运行的次数。该行仅在时才运行a[value] < N
,并且每次运行时,紧随其后的是一个尚未N
设置为的数组值N
,因此它最多可以运行,最多N
执行一次2N
循环迭代。
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)空间复杂度。
假设我们将此数组表示为单向图形数据结构-每个数字都是一个顶点,并且数组中的索引指向形成图形边缘的另一个顶点。
为了更简单起见,我们将索引0设置为n-1,并将数字范围设置为0..n-1。例如
0 1 2 3 4
a[3, 2, 4, 3, 1]
0(3)-> 3(3)是一个循环。
答:仅依靠索引遍历数组。如果a [x] = a [y],则为一个循环,因此重复。跳到下一个索引,然后再次继续下去,直到数组结尾。复杂度:O(n)时间和O(1)空间。
在下面的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');
}
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();
}
private static void printRepeating(int arr[], int size) {
int i = 0;
int j = 1;
while (i < (size - 1)) {
if (arr[i] == arr[j]) {
System.out.println(arr[i] + " repeated at index " + j);
j = size;
}
j++;
if (j >= (size - 1)) {
i++;
j = i + 1;
}
}
}
如果数组不是太大,则此解决方案比较简单,它将创建另一个相同大小的数组以进行滴答。
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)。
O(1)
空间。
a[a[i]]
,而O(1)空间约束则以该swap()
操作为关键提示这一事实。