首先记住一些位运算的常见操作:
1) 判断一个数 a 是否是 2 的幂(次方)
a & (a-1) == 0
2) a 异或 b-> a ^ b 可以用无进位相加的思路理解,这样理解更简单,在两数的二进制相加的操作中不进位,比如:
1 1 0
^ 0 1 0
= 1 0 0
其中第二位本来应该向高位进 1 但我们选择不进位,这样运算便能得到两数的异或结果
3) a^b^c^d^e 五个数随意调换位置,即其排列的异或结果相同
4) 通过异或交换两个数 a,b 的值
a = a ^ b;
b = a ^ b;
a = a ^ b;
5) 若一个装有 1~n 的整数的数组中随机拿出一个数,求拿出的那个数
求到总的异或和 xor1 (从1 一直异或到 n),数组剩余元素的异或和 xor2,xor1 ^ xor2 就是缺失的元素
6) 求 n 的二进制最低位所代表的数
n & (n取反+1) -> n & -n
7) a^b = c -> b = c^a , a = c^b
掌握了这些位运算技巧,让我们来几道位运算的题玩玩吧
应用:
力扣 136 题
给你一个 非空 整数数组
nums
,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
输入:nums = [2,2,1] 输出:1示例 2 :
输入:nums = [4,1,2,1,2] 输出:4示例 3 :
输入:nums = [1] 输出:1
提示:
1 <= nums.length <= 3 * 104
-3 * 104 <= nums[i] <= 3 * 104
- 除了某个元素只出现一次以外,其余每个元素均出现两次。
这道题可以运用 a^a = 0,a^0 = a 的特点解答
class Solution {
public int singleNumber(int[] nums) {
int n = nums[0];
for(int i=1;i<nums.length;i++){
n = n ^ nums[i];
}
return n;
}
}
是不是感觉很简单,那如果单次出现数字为两个呢
力扣 260 题
给你一个整数数组
nums
,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。你必须设计并实现线性时间复杂度的算法且仅使用常量额外空间来解决此问题。
示例 1:
输入:nums = [1,2,1,3,2,5] 输出:[3,5] 解释:[5, 3] 也是有效的答案。示例 2:
输入:nums = [-1,0] 输出:[-1,0]示例 3:
输入:nums = [0,1] 输出:[1,0]
提示:
2 <= nums.length <= 3 * 104
-231 <= nums[i] <= 231 - 1
- 除两个只出现一次的整数外,
nums
中的其他数字都出现两次
先异或所有元素得到两个只出现一次的数的异或结果 xor = a ^ b
拿到xor的最低位1所在的位置
a 与 b 的该位置的二进制数一定不同(这样异或结果才能为1),可以根据这个性质将原数组分为两类,一类是该位为1的,一位是该位为 0 的
将这两类放到两个数组中,a,b一定各占其一,这样我们就将问题简化为了在一个数组中找一个单独出现的元素
class Solution {
public int[] singleNumber(int[] nums) {
int xor = 0;
for(int n : nums){
xor ^= n;
}
int de = xor & -xor;//得到只有最低位为1,其他位置都为0代表的数
int xor1 = 0,xor2 = 0;
for(int n: nums){
if((n & de) == 0){//若指定位为1
xor1 ^= n;
}else{//若指定位为0
xor2 ^= n;
}
}
return new int[]{xor1,xor2};
}
}
再升级一下,若数组中有一个元素出现的次数少于 m 其余元素出现的次数等于 m,我们如何找到这个元素呢
力扣 137 题
给你一个整数数组
nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。
示例 1:
输入:nums = [2,2,3,2] 输出:3示例 2:
输入:nums = [0,1,0,1,0,1,99] 输出:99
提示:
1 <= nums.length <= 3 * 104
-231 <= nums[i] <= 231 - 1
nums
中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次
这道题 m 为 3,我们将所有元素转换成二进制来思考
维护数组中所有整数的三十二个位置1的总个数
若同一个数出现了 m 次,那么它的所有为 1 的位置中 1 的总个数一定为 m 的倍数
若一个数出现的次数小于 m ,那么它的所有为 1 的位置中 1 的总个数一定都不是 m 的倍数
我们将这些位置设置为1的二进制转为十进制返回即可
小贴士:
n >> x 代表将二进制 n 的所有位的数字向右移动 x 位,空缺补 0
n << x 代表将二进制 n 的所有位的数字向左移动 x 位,空缺补 0
我是这样记忆的,大家应该玩过齿轮式的密码锁,我们可以将其想象为拨动只有0、1数字的密码锁,>> 向右拨动,<< 向左拨动,每一次拨动密码锁都会拨动一个 0 出来
class Solution {
public int singleNumber(int[] nums) {
//统计每个位置1出现的次数
int m = 3;
int[] count = new int[32];
for(int n : nums){
for(int i=0;i<32;i++){
count[i] += (n & (1 << i)) != 0 ? 1 : 0;//若第i位为1就将第i位1的统计个数加1,为0就加0
}
}
//将1的个数不是 m 的倍数的位置设为1,返回答案
int ans = 0;
for(int i=0;i<32;i++){
if(count[i] % m != 0){
ans ^= 1 << i;//将第i位设置为1
}
}
return ans;
}
}