【基础算法】位运算-找出奇特的数

来源:互联网 发布:postfix ubuntu 编辑:程序博客网 时间:2024/06/11 04:51

Leetcode 里面有两道关于找出落单的数的题: single number 和 single number II

single number 这道题是让找出一个数组里唯一一个落单的数,其余的数都出现了2次

这道题首要想到的是异或,因为异或的性质中有A^A=0 0^A=A,这样就能找出那个只出现的1次的数,其余出现了2次的因为异或操作全部清零。

public int singleNumber(int[] A) {        int result=0;        for(int i=0; i<A.length; i++) {            result=result^A[i];        }        return result;    }

如果我们扩展到只有一个数出现了2k+1次,其他数都出现了2k次,这道题同样能够求解。2k次的数全部清零,2k+1次的前2k次也全部清零,这样就剩下那个你要找的数了。

异或的思路局限性非常大,因此当出现single number 2这样的题的时候,采用异或将无从下手。

single number 2这道题是让找出一个数组里唯一一个落单的数,其余的数出现了3次。我们会发现,异或只对偶数次的数有清零作用,奇数次的数将没有太多办法。

所以Code_Ganker大神提出一个方法是将每个数的二进制位存起来,即构建一个32size的数组用于存放所有的Integer。数组的每一个数向右移动i次再与上1(i=0 to 31)这样每个数的第i位就存在了这个32size的数组里(i=0 to 31)。

        int[] digit = new int[32];                for(int i=0; i<32; i++) {            for(int j=0; j<A.length; j++) {                digit[i]+=(A[j]>>i)&1;            }        }

这样,每个digit数组的元素要么是0,要么一定是3k或者3k+1,我们假设没有那个落单的数,这个时候数组的每个元素除了0,一定是3k,所以唯一造成3k+1的数就是那个落单的数,找出即可,因此只需要模上3,就可以找出那个落单的数。

        for(int i=0; i<32; i++)            result|=(digit[i]%3)<<i;

附上single number II 的完整代码 (作者是Code_Ganker)

public static int singleNumber(int[] A) {int result=0;int[] digit = new int[32];for(int i=0; i<32; i++) {for(int j=0; j<A.length; j++) {digit[i]+=(A[j]>>i)&1;}}for(int i=0; i<32; i++) {result|=(digit[i]%3)<<i;}return result;}

如果我们继续扩展,扩展成其他数出现了m次就一个出现了1次呢,这样数组的元素为mk或者mk+1。只需要模上m就可以了。

如果我们再继续扩展,扩展成其他数出现了m次,就一个出现了n次呢,这样数组的元素位mk或者mk+n。

如果m>n, 模上m后,mk%m=0, (mk+n)%m=n,这样可以通过发现不等于0的位来找出那个出现n次的数。这时候用1<<i代替(digit[i]%m)<<i即可。

如果m<n, 模上m后,mk%m=0, (mk+n)%m=? 未知!我们继续讨论

如果n是m的倍数,这样模上m后会全部被清0,所以不能用这样的方法。

如果n不是m的倍数,这样模上m后,n会出现剩余,此时可以用这种方法。

附上这种思想的代码

for (int i = 0; i < 32; i++) {if (digit[i] % 3 != 0)result |= 1 << i;}return result;






0 0
原创粉丝点击