前言
Leetcode算法系列:https://leetcode-cn.com/study-plan/algorithms/?progress=njjhkd2
简单总结一下位运算相关的算法题:
补码:正数的补码等于其原码;负数的补码等于:先得到其绝对值所表示的原码,然后将符号位置为 1,再对其余位取反(0-1,1-0),然后再加 1。
四位二进制的补码表示:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 |
-7 | -6 | -5 | -4 | -3 | -2 | -1 | -0 |
1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 | 1000 |
左移:<<,向左移动一位,右边补 0,相当于乘二。
右移:>>,向右移动一位,左边补上符号位(正数补 0,负数补 1),相当于除二。
无符号右移:>>>,向右移动一位,左边补 0。
一、2 的幂
题目链接:https://leetcode-cn.com/problems/power-of-two/
题目描述:
给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。
如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。
几种算法参考如下:
//在一些语言中,位运算的优先级较低,需要注意运算顺序。
public boolean isPowerOfTwo(int n) {
/*
if(n<=0)
return false;
//如果一个数是2的幂,那么其二进制表示中只能有一个 1
int num1=0;
for(int i=0;i<32;i++){//n 每次向右移,判断它的每一位是否为 1
if((n&1)==1)
num1++;
n>>=1;
}
if(num1==1)
return true;
return false;
*/
/*
if(n<=0)
return false;
int num1=0;
for(int i=0;i<32;i++){//令 1 每次向左移动, 判断其是否和 n 相等。
if((n&(1<<i))!=0)
num1++;
}
if(num1==1)
return true;
return false;
*/
//return n>0&& (n&(n-1))==0;
//return n>0 && (n&(-n))==n;//需要理解负数的补码表示方法
int BIG = 1 << 30;//这是int能够表示的最大的2的次方数了,即2 的30次方,判断是否为BIG的约数
return n > 0 && BIG % n == 0;
}
二、位1的个数
题目链接:https://leetcode-cn.com/problems/number-of-1-bits/
题目描述:
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
1,依次对每一位判断的算法参考如下:
public int hammingWeight(int n) {
//常规思路: n右移,或者将1左移。
/*
int num1=0,m=32;
while(m--!=0){
if((n&1)==1)
num1++;
n>>=1;
}
return num1; */
}
2,运算:n & (n−1),其运算结果恰为把 n 的二进制表示中的最低位的 1 变为 0 之后的结果。6 & 5=(0110)&(0101)=0100,这样就将 6 的二进制中最后一个 1 变为 0 了。
这样我们可以利用这个位运算的性质加速我们的检查过程,在实际代码中,我们不断让当前的 n 与 n−1 做与运算,直到 n 变为 0 即可。因为每次运算会使得 n 的最低位的 1 被翻转,因此运算次数就等于 n 的二进制位中 1 的个数。
算法参考如下:
public int hammingWeight(int n) {
int num1=0;
while(n!=0){
n=(n&(n-1));
num1++;
}
return num1;
}
三、颠倒二进制位
题目链接:https://leetcode-cn.com/problems/reverse-bits/
题目描述:
颠倒给定的 32 位无符号整数的二进制位。(将二进制位改为逆序:如 1101 变为 1011)
常规思路,对每一位依次判断,参考算法如下:
public int reverseBits(int n) {
int ans=0,num=31,tmp;
while(num--!=0){
tmp=(n&1);
ans=(ans|tmp);
n>>=1;
ans<<=1; // 多左移了一次。。。。。
}
tmp=((n>>31)&1);
ans=(ans|tmp);
return ans;
}
四、只出现一次的数字
题目链接:https://leetcode-cn.com/problems/single-number/
题目描述:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
采用异或运算⊕。异或运算有以下三个性质。1,任何数和 0 做异或运算,结果仍然是原来的数。(任何数和全1做异或,会将原来数中的1变为0,0变为1)2,任何数和其自身做异或运算,结果是 0。3,异或运算满足交换律和结合律。
参考算法如下:
public int singleNumber(int[] nums) {
int ans=0;
for(int n:nums)
ans^=n;
return ans;
}
熟悉一下集合操作(思路来源于官方题解):
1,使用集合存储数字。遍历数组中的每个数字,如果集合中没有该数字,则将该数字加入集合,如果集合中已经有该数字,则将该数字从集合中删除,最后剩下的数字就是只出现一次的数字。采用 HashSet,参考算法如下:
public int singleNumber(int[] nums) {
Set<Integer> set=new HashSet<>();
for(int i:nums){
if(!set.add(i))
set.remove(i);
}
int ans=0;
for(int i:set)
ans=i;
return ans;
}
或者使用 contains 方法:
public int singleNumber(int[] nums) {
Set<Integer> set=new HashSet<>();
for(int i:nums){
if(set.contains(i))
set.remove(i);
else
set.add(i);
}
int ans=0;
for(int i:set)
ans=i;
return ans;
}
2,使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。采用 HashMap,参考算法如下:
public int singleNumber(int[] nums) {
Map<Integer,Integer> map=new HashMap<>();
for(int i:nums){
if(map.containsKey(i))
map.put(i,map.get(i)+1);
else
map.put(i,1);
}
int ans=0;
for(int i:map.keySet())
if(map.get(i)==1)
ans=i;
return ans;
}
3,计算数组中的所有元素之和;然后依次将元素存储到集合中,如果发生重复,将这些发生重复的元素加起来,再乘以 2 即为原数组中重复元素之和;然后二者相减可得结果。参考算法如下:
public int singleNumber(int[] nums) {
Set<Integer> set=new HashSet<>();
int num=0,k=0;
for(int i:nums){
num+=i;
if(!set.add(i))
k+=i;
}
return num-2*k;
}
总结
完