由n个元素组成的数组,n-2个数出现了偶数次,两个数出现了奇数次,且这两个数不相等,如何用O(1)的空间复杂度,找出这两个数
思路分析:
方法一:涉及到两个数,就要用到异或定理了:若a^b=x,则a=b^x,b=x^a。对于这道题,假设这两个数分别为a、b,将数组中所有元素异或之后结果为x,因为a!=b,所以x=a^b,且x!=0,判断x中位为1的位数,只需要知道某一个位为1的位数k,如00101100,k可以取2或者3,或者5.因为x中第k位为1表示a或b中有一个数的第k位也为1,假设为a,将x与数组中第k位为1的数进行异或时,也即将x与a以及其他第k位为1的出现过偶数次的数进行异或,化简即为x与a异或,最终结果即为b。
程序示例如下:
#include "stdafx.h" #include <stdio.h> void FindElement(int a[], int length) { if (a == NULL || length <= 0) printf("数组中无元素,找个毛啊。"); else { int s = 0; int i; int k = 0; for (i = 0; i < length; i++) { s = s^a[i]; } int s1 = s; int s2 = s; while (!(s1 & 1)) { s1 = s1 >> 1; k++; } for (i = 0; i < length; i++) { if ((a[i] >> k) & 1) s = s^a[i]; } printf("%d %d\n", s, s^s2); } } int main() { int array[] = { 1, 2, 2, 3, 3, 4, 1, 5 }; int len = sizeof(array) / sizeof(array[0]); FindElement(array, len); getchar(); return 0; }
效果如图:
方法二:如果能够把原数组分为两个子数组,在每个子数组中,包含一个只出现一次的数字,而其他数字都出现两次,问题就可以很容易的解决了:分别对两个子数组执行异或运算。
首先从头到尾依次异或数组中的每一个数字,因为其他数字都出现了两次,在疑惑中全部抵消掉了,所以最终得到的结果将是两个只出现一次的数字的异或结果。而这两个数字肯定不一样,那么这个异或结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1,否则就为0了。在结果数字中找到第一个为1的位的位置,记为第N位,那么这两个数字一个第N位为1,另一个第N位为0,这样异或后结果数字的第N位才能为1.此时以第N位是不是1为标准把原数组中的数字分为两个子数组,第一个子数组中每个数字的第N位都为1,而第二个子数组的每个数字的第N位都为0.通过这种方法就可以把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。
代码如下:
#include "stdafx.h" #include <stdio.h> void findOnce(int data[], int n, int &num1, int &num2) { if (n < 5) return; int r1 = 0; for (int i = 0; i < n; i++) r1 = r1^data[i]; int bitNum = 0; while (!(r1 & 0x1)) { r1 = r1 >> 1; bitNum++; } int flag = (1 << bitNum); num1 = 0; num2 = 0; for (int j = 0; j < n; j++) { if (data[j] & flag) num1 = num1^data[j]; else num2 = num2^data[j]; } } int main() { int array[] = { 1, 2, 3, 2, 4, 3, 5, 1 }; int num1, num2; findOnce(array, sizeof(array) / sizeof(array[0]), num1, num2); printf("%d\n%d\n", num1, num2); getchar(); return 0; }
效果如图: