【剑指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的数字;
一个数与它的负数相与,两个情况,这个数是偶数还是奇数
- 偶数,结果是能整除这个偶数的最大的2的幂, 即: m = n & -n , 则 n % m = 0, 且 m = 2 ^ k;
- 奇数,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});
}
};
知识的价值不在于占有,而在于使用