目录
概览
题目1
题目2
解题
题目2
思路一:
局限性:
思路二:
题目1
思路一:
局限性:
思路二:
扩展
一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。
编写一个函数找出这两个只出现一次的数字。
例如:
有数组的元素是:1,2,3,4,5,1,2,3,4,6
只有5和6只出现1次,要找出5和6.
一个数组中只有一个数字是出现一次,其他所有数字都出现了两次。
编写一个函数找出这两个只出现一次的数字。
例如:
有数组的元素是:1,2,3,4,5,1,2,3,4,
只有5出现1次,要找出5
在解决题目1之前,我们先解决题目2作为预热,更有利于解决题目1和后续的拓展。
循环遍历,拿数组中的某一个元素遍历数组,直到找到与它相同的一个元素为止;
这个元素不能是他自己。
输出没有配对的元素。
实现源码:
int main()
{
int arr[] = {1,2,3,4,5,1,2,3,4};
int sz = sizeof(arr)/sizeof(arr[0]);
for(int i = 0;i < sz;i++)//遍历数组,用arr[i]去找arr[j],每一个arr[i] 都要遍历一边数组
{
int f = 0;
for(int j = 0;j < sz;j++)
{
if((i != j) && arr[i] == arr[j])//找到的arr[i]不能是arr[i]本身,所以限制i != j
{
f = 1;
break;
}
}
if(f == 0)
{
printf("%d ",arr[i]);
}
}
return 0;
}
思路一方法易想简单,但是代码量较大;
经计算,遍历算法时间复杂度是O(N^2)
参考异或运算的特性:
相同为0,不同为1,即
1 ^ 1 = 0
0 ^ 0 = 0
1 ^ 0 = 1我们可以推知异或的一个重要特性->自反性: A ^ B ^ B = A
由此,我们可以创建初始化为0的整型变量,记为(int tem = 0),让tem 异或数组中的每一个元素,得到的结果就是只有一个的元素ret;
实现源码:
int main()
{
int arr[] = {1,2,3,4,5,1,2,3,4};
int sz = sizeof(arr)/sizeof(arr[0]);
int ret = 0;
for(int i = 0;i < sz;i++)
{
ret ^= arr[i];
}
printf("%d",ret);
return 0;
}
思路二代码量有了很大的降低,经计算,时间复杂度为O(N),有了很大的提效。
一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。
编写一个函数找出这两个只出现一次的数字。
我们发现:题目一的要求更多了,有两个只出现一次的数字,怎样解决呢?
循环遍历,拿数组中的某一个元素遍历数组,直到找到与它相同的一个元素为止;
这个元素不能是他自己;
输出没有配对的元素。
实现源码:
int main()
{
int arr[] = {1,2,3,4,5,1,2,3,4,6};
int sz = sizeof(arr)/sizeof(arr[0]);
for(int i = 0;i < sz;i++)
{
int f = 0;
for(int j = 0;j < sz;j++)
{
if((i != j) && arr[i] == arr[j])
{
f = 1;
break;
}
}
if(f == 0)
{
printf("%d ",arr[i]);
}
}
return 0;
}
是不是和题目二的源码一样?为什么会这样?由于题目二并不会将所有的元素都异或在一个变量上,而是一个一个的去比对遍历,符合要求的单独输出,所以元素便于分离。
换个角度->如果我们还用异或的方法去把所有元素结合,得到的结果就是两个单独出现的元素的异或值了,难以分离。
对于遍历方法,有自己的优势,但是经过计算,遍历算法的时间复杂度是O(N^2),有待提速。
异或法不好分离,我们可以将两个单独出现一次的数字分在两个不同的数组中,再分别异或操作,这样不就好了?
(1) 先通过一次异或操作,重复元素会被抵消,最终结果相当于两个单次出现的元素的异或值;
2) 由异或规则可知,,若两个元素的异或值的某二进制位为1,则表示两个元素在该二进制位上的值不同(一个为0,另一个为1),找到其中一个满足条件(为1)的二进制位。(3) 根据(2)找到的二进制位,可以将原数组分成两个部分,两个单独出现的元素分别在两个部分,而相同的重复元素也会被分到同一个部分(因为其相应的二进制位的值是相同的);
(4) 对于两个部分再次进行异或操作,即相当于 排除偶次重复 问题,最终可以得到两个单独出现的元素。
补充:
(一般情况下,两个非零元素的异或值 != 0,那么这个异或值二进制位一定有1)
我们要找的二进制位可以是最右侧的二进制位,我们可以通过 ret &((~ret) + 1)的方式实现
假设N取 001010110000
运算 | 二进制结果 |
---|---|
N | 001010110000 |
~N | 110101001111 |
(~N)+1 | 110101010000 |
N&((~N)+1) | 000000010000 |
实现源码:
#include
int search(int arr[],int sz)//此函数用于求得数组中的所有元素的异或
{
int result = 0;
for(int i = 0;i < sz;i++)
{
result ^= arr[i];
}
return result;//其实就是5^6^0=5^6
}
int main1()
{
int arr[] = {1,2,3,4,5,1,2,3,4,6};
int sz = sizeof(arr)/sizeof(arr[0]);
int ret = search(arr,sz);
int bit_ret = ret &((~ret) + 1);//操作:取得二进制最右侧的1
int arr1[50] = {0};//存储
int arr2[50] = {0};
int c1 = 0;//计数
int c2 = 0;
for(int i = 0;i < sz;i++)
{
if((arr[i] & bit_ret) != 0)
{
arr1[c1] = arr[i];//不同
c1++;
}
else
{
arr2[c2] = arr[i];//相同
c2++;
}
}
printf("%d\n",search(arr1,c1));
printf("%d\n",search(arr2,c2));
return 0;
}
时间复杂的仍然为O(N),效率仍然较高。
用遍历算法可以处理任意个单个元素的情况,但是需要牺牲一些时间。
异或算法是一种高效的算法,但是需要多次分组,用到递归。
完~
未经作者同意禁止转载