问题来源:Find All Numbers Disappeared in an Array
好久没有刷题了,感受大脑开始迟钝,因此决定重拾刷题的乐趣。一开始不要太难,选一些经过率高的题目作,而后就看到了这个题目。我有些吃惊,这个题我虽然知道两种解法,但自己仍是有难度的,竟然经过率这么高。而后就搜索相关网页,看到一个和它很接近的题目《Find All Duplicates in an Array》,而后就释然了。这两个题目有相同的题干,只是问题略微不一样,解法有类似之处。估计是由于题号太接近了,会作第一个以后,第二个也很容易就作对了。本文主要解析《Find All Numbers Disappeared in an Array》,顺便简单解释一下《Find All Duplicates in an Array》。
该题的含义是:给定一个长度为n的数组,数组元素是1~n。可是有些元素出现一次,有些元素出现两次,从而也会致使有些元素不出现。如今让咱们找到哪些元素没有出现。另一个题目是让咱们找到出现两次的元素。时间复杂度O(n),空间复杂度O(1)。数组
第一种方法是利用以前博客《数组统计分析的另外一种方法》介绍的方法—元素归位法。元素归位法很容易理解,就是将n个元素交换到它应该在的位置。例如,元素5就放到位置4(下标从0开始)。这里须要注意一点,将某个元素交换到正确位置可能会致使当前位置的元素还不在正确位置,须要继续交换直到不能交换为止,伪代码以下:markdown
for i=1:n
while canSwap(i) do swap(i);
将元素归位以后,咱们就很容易得到哪些元素没有出现。当某个位置不是正确元素的时候,就意味着这个元素没有出现。也即针对没有出现的元素,咱们只须要返回下标;针对出现两次的元素,咱们只须要返回该位置的值。
这里有一个疑问,伪代码有两个for循环,复杂度是否是O(
C语言版代码以下:app
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、一、2,i表示该位置原本的元素。在第二次循环中,由于i的范围是1~n,因此除以(n+1)就等于0,从而咱们就得到了k的值。根据k的值,咱们就很容易知道哪些元素没有出现,哪些元素出现了屡次。
C语言版代码以下:ui
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已经出现过一次,咱们不作任何操做。无论一个元素出现一次仍是两次,只要出现它对应的位置就会被取负。当某个元素不出现的时候,该元素对应的位置始终访问不到,因此仍是正值,经过这种方法咱们就能够找到哪些元素没有出现。
经过上面的分析咱们也很容易知道,在取负的过程当中,若是发现要取负的位置已经为负,说明这个元素已经出现过,也即该元素出现了两次,咱们能够将该元素保留下来。
C语言版代码以下:spa
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语言代码以下:.net
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;
}