来自LeetCode 136、137、260
除了某k个元素只出现一次以外,其余每个元素均出现m次。找出那个只出现了k次的元素。
目录
O、位运算技巧用法总结
- 向下整除 n / 2 等价于 右移一位
n >> 1
- 判断奇偶时,取余数 n%2 等价于 判断二进制最右一位值
n & 1
(n−1)
: 二进制数字 n 最右边的 1 变成 0 ,此 1 右边的 0 都变成 1 。n & (n−1)
: 二进制数字 n 最右边的 1 变成 0 ,其余不变。- 异或运算(二进制x):
x ^ 0 = x
,x ^ 1 = ~x
- 与运算(二进制x):
x & 0 = 0
,x & 1 = x
异或运算
可以帮助我们消除出现偶数次的数字。0 与任何数 XOR 结果为该数;两个相同的数 XOR 结果为 0。则只有某个位置的数字出现奇数次时,该位的掩码才不为 0。我们计算bitmask ^= x
,则 bitmask留下的就是出现奇数次的位。x & (-x)
保留位中最右边 1 ,且将其余的 1 设为 0。i >> j & 1
: i的第j位是否是1- n的二进制表示中第k位是几:
n >> k & 1
一、某个元素只出现一次以外,其余每个元素均出现两次(k=1,m=2)
思路一:位运算
所有的数字异或,剩下的就是出现次数为奇数次的那个数字。
class Solution {
public int singleNumber(int[] nums) {
int ans = 0;
for(int num : nums){
ans ^= num;
}
return ans;
}
}
二、某个元素只出现一次以外,其余每个元素均出现三次(k=1,m=3)
思路一:位运算,遍历统计
统计所有数字的各二进制位中 1 的出现次数,并对 3求余,结果则为只出现一次的数字。按位遍历统计。
此方法可扩展到k=1,m取任何值的情况。
class Solution {
public int singleNumber(int[] nums) {
int[] counts = new int[32];
for(int num : nums){
//每个数字都有32位
for(int j = 0; j < 32; j++){
//更新第j位,即这个数字的第j位是0还是1,写到counts里
counts[j] += num & 1;
//无符号右移,第j位到第j+1位
num >>>= 1;
}
}
int res = 0;
// 修改求余数值m,即可实现解决除了一个数字以外,其余数字都出现m次的通用问题
int m = 3;
for(int i = 0; i < 32; i++){
res <<= 1;
//只出现一次的数字的第(31 - i)位,恢复到res中
res |= counts[31-i] % m;
}
return res;
}
}
思路二:有限状态自动机
参考
出现三次的数字,各 二进制位出现的次数都是3的倍数。用two(高位),one(低位)来表示对3取余的这三个状态。
因为相同的数字某一位是0或者1的情况是相同的。对一位来看,来一个数字,如果该位是1,则从00—>01,再来一个数字,如果新数字的该位还是1,则01—>10。
- 关于状态转换公式的推导