【剑指offer】【位运算】56 - I. 数组中数字出现的次数

题目链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/

思路

问题简化

考虑一个简单问题:数组中所有数组只有一个数出现了一次,其它数字都出现了两次?
利用异或的性质,两数相同,异或结果为0,所有数异或,异或的结果就是答案;因此,我们想办法把这个数组分成两组,一组只有一个数字出现一次;然后分别对两个数组进行异或,得到的就是答案;

关键点

关键在于如何将数组分组?
由于两个不相同的数,a,b,异或的结果肯定不为0,肯定有一位是1,我们只需要按照第k位是否为1对数组进行划分。

划分的方法

x & -x

利用一个数与它的负数相与来获得最后一位为1的数字;
一个数与它的负数相与,两个情况,这个数是偶数还是奇数

  1. 偶数,结果是能整除这个偶数的最大的2的幂, 即: m = n & -n , 则 n % m = 0, 且 m = 2 ^ k;
  2. 奇数,x & -x 的结果一定是1;
    所以,可以得到s二进制为1的其中1位

直接找s的哪一位为1

int k = 0;
while (!(sum >> k & 1)) k ++ ;

位运算

实现1

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int s = 0;
        //数组中所有的数都进行异或,最后得到的结果s是两个只出现一次的数a和b异或的结果
        //由于a与b不一样,那么s肯定不是0,那么s的二进制肯定至少有1位是1,只有0^1 = 1;
        //所以在第n位,有两种情况:1) a为0,b为1;2)a为1,b为0;
        for(int num : nums)
            s ^= num;
        //s & -s可以得到s的二进制中哪一位为1,其中一位;
        int k = s & (-s);
        //分组异或
        vector<int> rs(2, 0);
        for(int num : nums)
        {
            //第一组
            if(num & k) rs[0] ^= num;
            //第二组
            else rs[1] ^= num;
        }
        return rs;
    }
};

实现2

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int sum = 0;
        for (auto x : nums) sum ^= x;
        int k = 0;
        while (!(sum >> k & 1)) k ++ ;
        int first = 0;
        for (auto x : nums)
            if (x >> k & 1)
                first ^= x;
        return vector<int>({first, sum ^ first});
    }
};

参考:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/solution/c-0ms-chao-ji-xiang-xi-jie-shi-kan-bu-dong-de-ji-2/

posted @ 2020-05-06 20:32  NaughtyCoder  阅读(110)  评论(0)    收藏  举报