交换两个整数
思路:给定a,b用位运算交换两个数的值:
a = a ^ b;
b = a ^ b;//b = a ^ b ^ b (这里a,b是初始a,b)
a = a ^ b;//a = a ^ b ^ a ^ b ^ b (这里a,b是初始a,b)
461.汉明距离
思路:对两个数进行异或操作,位级表示不同的那一位为 1,统计有多少个 1 即可。不断的判断最低位是否为1,为一则计数加一,然后将该数右移一位:
public int hammingDistance(int x, int y) {
int z = x ^ y;
int cnt = 0;
while(z != 0) {
if ((z & 1) == 1) cnt++;
z = z >> 1;
}
return cnt;
}
当然,还有一种比较简单的方法,z&(z-1)操作可以每次消去z中的一个1,所有1消去完全后z就等于0:
while (z != 0) {
z &= (z - 1);
cnt++;
}
另外,还可以调用相应的API:
Integer.bitCount(x ^ y)
这一题和牛客:二进制中1的个数相似,这里题目中负数用补码表示。
补码是这样的:
下面求-9 补码:先减一:0000 1001 - 1 = 0000 1000;
再取反:1111 0111。
所以有:-9 补码 = 1111 0111。
有人可能会疑惑负数怎么办,其实z &= (z - 1)中减一就相当于把原来的数最右边的一个1变为0,再与原数进行与操作,就能得到这个位置左边保持不变,右边(包括它自己)变为0。
136.唯一数字
思路:找出数组中唯一一个不重复的元素。两个相同的数异或的结果为 0,对所有数进行异或操作,最后的结果就是单独出现的那个数。
public int singleNumber(int[] nums) {
int ret = 0;
for (int n : nums) ret = ret ^ n;
return ret;
}
476. 数字的补数
思路:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
除了Map我并没有想出别的方法,因为我实在不擅长位运算。。。不过我在评论里找到了一种更容易理解的方法,我觉得太巧妙了!!
每个数想象成32位的二进制,对于每一位的二进制的1和0累加起来必然是3N或者3N+1, 为3N代表目标值在这一位没贡献,3N+1代表目标值在这一位有贡献(=1),然后将所有有贡献的位|起来就是结果。这样做的好处是如果题目改成K个一样,只需要把代码改成cnt%k,很通用。
public int singleNumber(int[] nums) {
int ret = 0;
for (int i = 0; i < 32; i++) {
int mask = 1 << i;
int cnt = 0;
for (int j = 0; j < nums.length; j++) {
if ((nums[j] & mask) != 0) {
cnt++;
}
}
if (cnt % 3 != 0) {
ret |= mask;
}
}
return ret;
}
268.缺失数字
思路:数组元素在 0-n 之间,但是有一个数是缺失的,要求找到这个缺失的数。原理和上一题类似,可以肯定数组中是存在 0-n 的数的(除了那个缺失的),所以我们将数组中所有数和 0-n 异或,相同的数就抵消,最后只剩下那个缺失的数:
public int missingNumber(int[] nums) {
int ret = 0;
for (int i = 0; i < nums.length; i++) {
ret = ret ^ i ^ nums[i];
}
return ret ^ nums.length;
}
剑指 Offer 56 - I. 数组中数字出现的次数
思路:
// 假设结果数为A B
public int[] singleNumbers(int[] nums) {
int x = 0; // 用于记录 A B 的异或结果
/** 得到A^B的结果
基于异或运算的以下几个性质
1. 交换律
2. 结合律
3. 对于任何数x,都有x^x=0,x^0=x
*/
for (int val : nums) x ^= val;
// x & (-x)本身的作用是得到最低位的1,
int flag = x & (-x);
// 而我们所需要的做到的是:利用这个1来进行分组,也就是做到将A和B区分开
// 前面已经知道,x是我们需要的结果数A和B相异或的结果,也就是说,x的二进制串上的任何一个1,都能成为区分A和B的条件
// 因此我们只需要得到x上的任意一个1,就可以做到将A和B区分开来
int res = 0; // 用于记录A或B其中一者
// 分组操作
for (int val : nums) {
// 根据二进制位上的那个“1”进行分组
// 需要注意的是,分组的结果必然是相同的数在相同的组,且还有一个结果数
// 因此每组的数再与res=0一路异或下去,最终会得到那个结果数A或B
// 且由于异或运算具有自反性,因此只需得到其中一个数即可
if ((flag & val) != 0) {
res ^= val