题目:
面试题56 - I. 数组中数字出现的次数
260. 只出现一次的数字 III
题解:
题解1:分组异或
注意:
x & -x 表示的含义是:获得 x 中最低位的1。
题解2:位运算+循环左移(优选)
- 参考链接:
LCR 177. 撞色搭配(位运算,清晰图解)
class Solution {
public int[] singleNumbers(int[] nums) {
//因为相同的数字异或为0,任何数字与0异或结果是其本身。
//所以遍历异或整个数组最后得到的结果就是两个只出现一次的数字异或的结果:即 z = x ^ y
int z = 0;
for(int i : nums) z ^= i;
//我们根据异或的性质可以知道:z中至少有一位是1,否则x与y就是相等的。
//我们通过一个辅助变量m来保存z中哪一位为1.(可能有多个位都为1,我们找到最低位的1即可)。
//举个例子:z = 10 ^ 2 = 1010 ^ 0010 = 1000,第四位为1.
//我们将m初始化为1,如果(z & m)的结果等于0说明z的最低为是0
//我们每次将m左移一位然后跟z做与操作,直到结果不为0.
//此时m应该等于1000,同z一样,第四位为1.
int m = 1;
while((z & m) == 0) m <<= 1;
//我们遍历数组,将每个数跟m进行与操作,结果为0的作为一组,结果不为0的作为一组
//例如对于数组:[1,2,10,4,1,4,3,3],我们把每个数字跟1000做与操作,可以分为下面两组:
//nums1存放结果为0的: [1, 2, 4, 1, 4, 3, 3]
//nums2存放结果不为0的: [10] (碰巧nums2中只有一个10,如果原数组中的数字再大一些就不会这样了)
//此时我们发现问题已经退化为数组中有一个数字只出现了一次
//分别对nums1和nums2遍历异或就能得到我们预期的x和y
int x = 0, y = 0;
for(int i : nums) {
//这里我们是通过if...else将nums分为了两组,一边遍历一遍异或。
//跟我们创建俩数组nums1和nums2原理是一样的。
if((i & m) == 0) x ^= i;
else y ^= i;
}
return new int[]{x, y};
}
}
代码:
代码1:分组异或
/**
* 面试题56_1
*/
public class 面试题56_1 {
public static int[] singleNumbers(int[] nums) {
int sum = 0;
int res[] = new int[2];
// sum用于将所有的数异或起来
for(int i = 0; i < nums.length; i++)
{
sum = sum ^ nums[i];
}
// 获得sum中最低位的1
int lowbit = sum & (-sum);
for(int i = 0; i < nums.length; i++)
{
if((nums[i] & lowbit) == 0) // 如果该位为0,就分到第一组
{
res[0] = res[0] ^ nums[i];
}
else // 如果该位不为0,就分到第二组
{
res[1] = res[1] ^ nums[i];
}
}
return res;
}
public static void main(String[] args) {
int nums[] = { 1,2,10,4,1,4,3,3 };
int res[] = singleNumbers(nums);
for(int i = 0; i < res.length; i++)
{
System.out.print(res[i] + " ");
}
}
}
代码2:位运算+循环左移(优选)
public class 面试题56_1 {
public static void main(String[] args) {
int nums[] = {1, 2, 10, 4, 1, 4, 3, 3};
int res[] = sockCollocation(nums);
for (int i = 0; i < res.length; i++) {
System.out.print(res[i] + " ");
}
}
public static int[] sockCollocation(int[] sockets) {
int z = 0;
for (int i = 0; i < sockets.length; i++) { // 遍历 sockets 执行异或运算
z ^= sockets[i];
}
int m = 1;
while ((z & m) == 0) { // m 循环左移一位,直到 z & m != 0
m <<= 1;
}
int x = 0;
int y = 0;
for (int i = 0; i < sockets.length; i++) {
if ((sockets[i] & m) == 0) { // 若 num[i] & m == 0 , 划分至子数组1 ,执行遍历异或
x ^= sockets[i];
} else { // 若 num[i] & m != 0 , 划分至子数组2 ,执行遍历异或
y ^= sockets[i];
}
}
int res[] = new int[] {x, y}; // 遍历异或完毕,返回只出现一次的数字 x 和 y
return res;
}
}