找出唯一出现一次的数
1.问题描述:找出数列中唯一一个出现一次的数,其余得数都出现两次。
分析:
1)最笨的方法当然是穷举了:
#include <stdio.h> int array[] = { 1 , 3, 3, 1, 5, 5 , 6}; int get_only() { int *ptmp1, *ptmp2; int *arrend = array + sizeof(array) / sizeof(int); for(ptmp1 = array; ptmp1 != arrend; ptmp1 ++) { for (ptmp2 = array; ptmp2 != arrend; ptmp2++) { if (ptmp2 != ptmp1 && *ptmp2 == *ptmp1) { break; } } if (ptmp2 == arrend) { return ptmp1 - array; } } if (ptmp1 == arrend) { return -1; } } int main(int argc, char *argv[]) { int rst = get_only(); if (rst == -1) printf("no such number\n"); else printf("only num is : %d\n", array[rst]); return 0; }
2)异或操作 (^):
性质: 0 ^ 0 = 0, 1 ^ 1 = 0, 0 ^ 1 = 1, 0 ^ A = A;
总结起来就是按位异或 相同为0,不同为1。
这样一来明显得出,相同的俩个数异或得0,不同的两个数异或一定不为0(多少另算),0和任和数异或都得其本身(0 ^ A = A)。
由此得出另一种方法,将数组中的所有元素进行异或操作,则剩下的一个必定是唯一一个只出现一次的数:
#include <stdio.h> int array[] = { 1 , 3, 3, 1, 5, 5 , 6 }; int get_only2() { int ret = array[0]; int i; printf("len is %d\n", len); for (i = 1; i < sizeof(array) / sizeof(int); i++) { printf("ret is %d\n", ret); ret = ret ^ array[i]; } return ret; } int main(void) { int ret = get_only2(); printf("only number is %d\n", ret); return 0; }
btw:运用异或操作还可完成不借助其它参数,交换两个数的值: (用 g++编译,gcc编译不过)
#include <stdio.h> void swap(int &a, int &b) { a = a ^ b; b = a ^ b; a = a ^ b; } int main(void) { int a = 1; int b = 2; printf("before swap: a %d, b %d\n", a, b); swap(a, b); printf("after swap: a %d, b %d\n", a, b); return 0; }
-----------------------------------------------------------------------分割线----------------------------------------------------------
2.问题描述:数列中,只有两个整数只出现一次,其余的出现两次,找出这两个整数.
有了上边的铺垫,很容易想到用 异或 操作,通常回想到将所有数异或。
但是发现,若这两个数分别为A, B,则异或后 只能消除其它存在两次的数,但是结果为A ^ B,仍然找不出A, B。
此时我们希望A和B, 分别出现在两个数组中,并且这两个数组中的各自的其它成员,是两两相同的。然后这两个数组分别各自异或操作,则数组各自只剩下A和B,这样便找出来了。
但是我们如何分出这样的两个数组呢?
根据之前分析得出,A和B不相等则 A ^ B 的结果必然不为0,那么这个结果必定可以用16进制表示,并且至少某一位上为1.我们假设从右向左的第x位不为0,即为1.(0x001001...,1的位置为x)
那么肯定A的第x位为1,B的为0;或者相反。同理,除A,B以外的这些数的第x位上或者为0,或者为1. 此时便可根据第x位为0或者为1,将整个数列分成两个数组且分别包含A和B,姑且叫arrA, arrB。
这样一分,我们还发现,arrA中除A,以外是两两相同的,arrB除B以外也是两两相同的。最极端的情况是arrA只包含A或者arrB只包含B。但是这不重要,重要的是我们得到了两个数组分别包含A和B且其余两两相同。
于是,我们分别将arrA中元素异或操作得到A,将arrB中元素异或操作得到B。
步骤:(g++编译)
1)先得到A ^ B (get_oxr_rst)
2)找到第x位上的1,这里的技巧是得到一个mask,只处理这一位 (get_mask)。
3)用mask & arr[idx], 找到这一位为1的所有元素,并异或操作。
4)B = orx_rst ^ A.
#include <stdio.h> int get_oxr_rst(int *arr, int len) { int rst = 0; int idx; for(idx = 0; idx < len; idx ++) { rst ^= arr[idx]; } return rst; } int get_mask(int oxr_rst) { int mask = 1; while((mask & oxr_rst) == 0) { mask <<= 1; } return mask; } int get_rst(int *arr, int len, int &A, int &B) { if (len < 2) { return 0; } int oxr_rst = get_oxr_rst(arr, len); int mask = get_mask(oxr_rst); int idx; for (idx = 0; idx < len; idx++) { if (mask & arr[idx]) { A ^= arr[idx]; } } B = A ^ oxr_rst; return 1; } int main(int argc, char *argv[]) { int arr[] = { -4, 2, 2, 4 , 3, 5, 5, 3}; int A = 0, B = 0; int rst = get_rst(arr, sizeof(arr) / sizeof(int), A, B); if (rst) printf("different number is A %d and B %d\n", A, B); else printf("wrong args\n"); return 0; }
3.问题描述:数列中,有三个整数只出现一次,其余的出现两次,找出这三个整数.
思路1):同样如果按照上边思路来,假设三个不同的值是A, B, C。首先得到 rst = A^B^C
找到rst的十六进制从右到左第一个1。如果A,B,C在这个位上的值为分别001或010或100。则这种情况下仍可以求出一个mask,
进而可以将数列分成两组,其中一个必定包括在该位为1的数,假设为A,然后遍历数组,将 arr[idx] & mask != 0的所有值异或,这样便得到了A,然后就简化成了第二个问题。
但是,rst的十六进制从右到左第一个1,还可能是111这个情况,这样一来便无法用思路1来解,因为我们不能知道A,B,C的第x位是否是111,还是001或010或100,只有是后者才能用常规思路。
到这里得到结论 思路1是行不通的。那么看思路2.
思路2):
(1)另x = (A ^ B), y = (B ^ C), z = (C ^ A)
(2)我们有 (A^B)^(B^C)^(C^A) = x ^ y ^ z = (rst ^ C) ^(rst ^ A) ^(rst ^ B) = 0
(3)x != 0, y != 0, z != 0 , 且 x != y != z, 因为两个相等的数 异或 才等于零,而A, B, C是互不相等的。
(4)x ^ y ^ z = 0 且 x != y != z != 0, 那么x, y, z每位上的组合只能是000或110,这样才能保证x ^ y ^ z = 0,
同时又不可能所有位组合全为 000,那样 x = y = z = 0与已知矛盾.由此得 x, y, z中十六进制从右向左数第一次肯定会出现110。
假设首次出现110组合是从右向左数第m位,且两个1来自 x和y,则可以得到z的m位是0,并且,z第一次出现1的位置在m的左边,即可能是
从右向左数(m + k = n, k > 0)的位置。举例 若x = 0x1001, y = 0x0001, 则一定有z = 0x1000。
好了,在继续解释之前,我们要知道 rst ^ arr[i] 的结果集中,肯定包含x, y, z.因为x = rst ^ C, y = rst ^ A, z = rst ^ B,而A, B, C是arr中元素,
同时,如果我们将 rst ^ arr[i]得到结果集中的每个新元素,按照其从右向左数第一次出现的1是否在第n位上进行分组,则可以得到两组数v1,v2,其中一组包含
z,另外一组包含x,y。假设包含z的为v1, 包含x,y的为v2. 那么同时这样又将arr数组元素分为两组arr1, arr2,其中arr1中包含B, arr2中包含A,C.我们将 arr1中的所有
元素异或操作,从而可以得到B。
(5)找到一个了,另外两个就好找了。
#include <stdio.h> struct diff_values { int first; int second; int third; }; static int get_oxr_rst(int *arr, int len) { int rst = 0; int idx; for(idx = 0; idx < len; idx ++) { rst ^= arr[idx]; } return rst; } //a new method to get the mask without using a loop static int get_first_onebit_mask(int value) { return value & ~(value - 1); } static int get_mask(int *arr, int len, int oxr_rst) { int idx; int mask = 0; /* if arr[i] = arr[j] then oxr_rst ^ arr[i] = oxr_rst ^ arr[i] then get_first_onebit_mask(oxr_rst ^ arr[i]) = get_first_onebit_mask(oxr_rst ^ arr[j]) then this operation cross out all arr[i] = arr[j]. then we get: mask = get_first_onebit_mask(oxr_rst ^ (arr[i]) //(arr[i] = A) ^ get_first_onebit_mask(oxr_rst ^ arr[j]) //(arr[j] = B) ^ get_first_onebit_mask(oxr_rst ^ arr[k]) //(stt[k] = C) then mask is what we want whose first "1" is just on postion n */ for (idx = 0; idx < len; idx++) { //mask ^= get_first_onebit_mask(oxr_rst ^ arr[idx]); int tmpmask = get_first_onebit_mask(oxr_rst ^ arr[idx]); mask ^= tmpmask; } return mask; } static int get_rst2(int *arr, int len, struct diff_values &dv) { if (len < 2) { return 0; } int oxr_rst = get_oxr_rst(arr, len); int mask = get_first_onebit_mask(oxr_rst); int idx; int first = 0; for (idx = 0; idx < len; idx++) { if (mask & arr[idx]) { first ^= arr[idx]; } } dv.second = first; dv.third = first ^ oxr_rst; return 1; } int get_rst(int *arr, int len, struct diff_values &dv) { if (len < 3) { return 0; } int oxr_rst = get_oxr_rst(arr, len); int mask = get_mask(arr, len, oxr_rst); int idx; int first = 0; for (idx = 0; idx < len; idx++) { if (get_first_onebit_mask(oxr_rst ^ arr[idx]) == mask) //this operation equals to get v1. { first ^= arr[idx]; //this equals to the oxr operation in arr1 which contains B as the example // and the result is one of value of A, B , C. } } //when we get there, it means we get one of the three unique numbers; //then we get the left two numbers; //exchange first with arr[len - 1], for (idx = 0; idx < len; idx++) { if (first == arr[idx]) { arr[idx] = arr[idx] ^ arr[len - 1]; arr[len - 1] = arr[idx] ^ arr[len - 1]; arr[idx] = arr[idx] ^ arr[len - 1]; break; } } dv.first = first; //only pass len - 1 values into arr, so that we can delete value first get_rst2(arr, len - 1, dv); return 1; } int main(int argc, char *argv[]) { int arr[] = { 3, 3, -4,5, 5, 8, 9 }; struct diff_values dv; int rst = get_rst(arr, sizeof(arr) / sizeof(int), dv); if (rst) printf("different number is A %d , B %d , C %d\n", dv.first, dv.second, dv.third); else printf("wrong args\n"); return 0; }
参考:
http://www.cnblogs.com/luxiaoxun/archive/2012/09/08/2676610.html
https://www.lijinma.com/blog/2014/05/29/amazing-xor/
http://baike.baidu.com/link?url=SeBrbp2jNqe1N-hF_j4UWt953O7ra73Seo__jW2uEbXeRuo-wLTKkROZZAV_jCTLICKw7oSQQRQmZovRCaFYkcC3MIS2jSd7wR9kVVIbjKm
以后碰到类似的题,继续补充,欢迎指正与指点。