问题来源:​​Find All Numbers Disappeared in an Array​

非常久没有刷题了,感觉大脑開始迟钝,所以决定重拾刷题的乐趣。

一開始不要太难,选一些通过率高的题目做,然后就看到了这个题目。我有些惊讶,这个题我尽管知道两种解法。但本身还是有难度的。竟然通过率这么高。然后就搜索相关网页。看到一个和它非常接近的题目《Find All Duplicates in an Array》。然后就释然了。这两个题目有同样的题干,仅仅是问题稍微不同,解法有类似之处。

预计是由于题号太接近了,会做第一个之后。第二个也非常easy就做对了。

本文主要解析《Find All Numbers Disappeared in an Array》,顺便简单解释一下《Find All Duplicates in an Array》。

该题的含义是:给定一个长度为n的数组,数组元素是1~n。可是有些元素出现一次。有些元素出现两次。从而也会导致有些元素不出现。

如今让我们找到哪些元素没有出现。另外一个题目是让我们找到出现两次的元素。时间复杂度O(n),空间复杂度O(1)。

解法一

第一种方法是利用之前博客《数组统计分析的还有一种方法》介绍的方法—元素归位法。元素归位法非常easy理解,就是将n个元素交换到它应该在的位置。

比如。元素5就放到位置4(下标从0開始)。

这里须要注意一点,将某个元素交换到正确位置可能会导致当前位置的元素还不在正确位置,须要继续交换直到不能交换为止,伪代码例如以下:

for i=1:n  
while canSwap(i) do swap(i);


将元素归位之后。我们就非常easy获得哪些元素没有出现。当某个位置不是正确元素的时候,就意味着这个元素没有出现。

也即针对没有出现的元素,我们仅仅须要返回下标;针对出现两次的元素,我们仅仅须要返回该位置的值。

这里有一个疑问,伪代码有两个for循环,复杂度是不是O(n2)呢?不是,复杂度还是O(n),这个能够通过均摊分析来解释:假设满足交换条件。则每次都会使一个元素处在正确位置。由于总共同拥有n个元素。所以至多须要n-1次交换(交换完n-1个元素,第n个元素自己主动满足)就可以使全部的元素处在正确位置,也即while循环至多运行O(n)次,每次的平摊代价是O(1)。所以上述交换操作的复杂度为O(n)。

C语言版代码例如以下:

void swap(int *a,int *b)  
{
int temp=*a;
*a=*b;
*b=temp;
}

int* findDisappearedNumbers(int* nums, int numsSize, int* returnSize) {
int* result=(int*)malloc(sizeof(int)*numsSize);

for(int i=0;i<numsSize;i++)
{
while(nums[i]!=i+1&&nums[nums[i]-1]!=nums[i])
{
swap(&nums[i],&nums[nums[i]-1]);
}
}

*returnSize=0;

for(int i=0;i<numsSize;i++)
{
if(nums[i]!=i+1)
{
result[*returnSize]=i+1;
*returnSize=*returnSize+1;
}
}

return result;
}


解法二

在待字闺中公众号《数组统计分析》中,陈利人老师以前给出第二种解法—取余法,道理也比較好理解。数组的元素范围为1~n,第一次循环首先把每一个元素相应的位置加上(n+1)。第二次循环把每一个位置除以(n+1),假设该位置为0,表示某个元素没有出现;假设该位置等于2,表示出现两次。

原理是什么呢?在第一次循环中,我们事实上是将每一个位置变成k*(n+1)+i,当中k表示该位置加(n+1)的次数。取值为0、1、2。i表示该位置本来的元素。在第二次循环中,由于i的范围是1~n。所以除以(n+1)就等于0,从而我们就获得了k的值。

依据k的值。我们就非常easy知道哪些元素没有出现。哪些元素出现了多次。

C语言版代码例如以下:

int* findDisappearedNumbers(int* nums, int numsSize, int* returnSize) {
int* result=(int*)malloc(sizeof(int)*numsSize);

*returnSize=0;

for(int i=0;i<numsSize;i++)
{
nums[nums[i]%(numsSize+1)-1]+=(numsSize+1);
}

for(int i=0;i<numsSize;i++)
{
if(nums[i]/(numsSize+1)==0)
{
result[*returnSize]=i+1;
*returnSize=*returnSize+1;
}
}

return result;
}


解法三

在Top Solution中,有网友分享了一种非常奇异的解法—取负法。

含义是:将元素相应的位置取负。简单一句话可能不好理解,我们举个样例。

假设在位置k放了元素i,则在取负的过程中i的取值有两种可能:为正,表示当前尚未遇到元素k将该位置取负;为负,表示当前已经有元素k出现,并将元素取负。

可是我们不关心k,我们关心元素i。

元素i既然出现,我们就看一下位置i:为正,表示这是元素i第一次出现,我们将位置i取负;为负。表示元素i已经出现过一次,我们不做不论什么操作。无论一个元素出现一次还是两次,仅仅要出现它相应的位置就会被取负。

当某个元素不出现的时候,该元素相应的位置始终訪问不到,所以还是正值,通过这样的方法我们就能够找到哪些元素没有出现。

通过上面的分析我们也非常easy知道,在取负的过程中。假设发现要取负的位置已经为负。说明这个元素已经出现过,也即该元素出现了两次,我们能够将该元素保留下来。

C语言版代码例如以下:

int* findDisappearedNumbers(int* nums, int numsSize, int* returnSize) {
int* result=(int*)malloc(sizeof(int)*numsSize);

for(int i=0;i<numsSize;i++)
{
int index=abs(nums[i])-1;

if(nums[index]>0) nums[index]=-nums[index];
}

*returnSize=0;

for(int i=0;i<numsSize;i++)
{
if(nums[i]>0)
{
result[*returnSize]=i+1;
*returnSize=*returnSize+1;
}
}

return result;
}


针对《Find All Duplicates in an Array》,採用取负法实现的C语言代码例如以下:

int* findDuplicates(int* nums, int numsSize, int* returnSize) {
int* result=(int*)malloc(sizeof(int)*numsSize);

*returnSize=0;

for(int i=0;i<numsSize;i++)
{
int index=abs(nums[i])-1;

if(nums[index]>0)
{
nums[index]=-nums[index];
}
else
{
result[*returnSize]=index+1;
*returnSize=*returnSize+1;
}
}

return result;
}