二进制的基础
java里位运算符是对补码处理的, java里取反符号~会导致符号位改变
- 计算机里采用补码存储数值
对于正数,其补码、原码和反码都是相同的。即,正数的补码就是其本身。
对于负数,补码的计算过程相对复杂一些,但遵循固定的步骤:求原码:负数的原码是其绝对值的二进制表示,但最高位(符号位)为1。
求反码:将原码的符号位保持不变,其余各位取反(即0变为1,1变为0)。
求补码:在反码的基础上加1,即可得到负数的补码。所以求源码,需要减1再取反(符号位不变)
仔细想想,取反加1获得的补码,其源码为原来的相反数
获取最低位1的状态只需&1即可
java里逻辑移位(>>>)以及算数位移(>>)
逻辑位移会默认将缺的位补0,那么一个数逻辑右移后最高位默认是0
算数位移则是最高位保持原来状态,原来最高位是几,移位后还是几
异或
本质为不进位相加 1^1=0 0^0=0 0^1=1 可以看出一个数异或任意另外一个数 偶数次,该数保持不变,如a^b^b=a
那么就有以下操作
public static void swap(int a,int b) { //交换两个数
a^=b; //a^b
b^=a; //b^a^b
a^=b; //a^b ^b^a^b
System.out.println(a); //打印b的值
}
题目:找到缺失的数
给一个数组,里面包含0~9这10个数,但是缺失了一个数,使得数组长度为9,在不用哈希情况下找到这个数.
public static void main(String[] args){
int[] arr=new int[] {1,2,3,4,6,7,8,9,0}; //对于这个测试用例来说其缺失了5
int cur=0;
for(int i:arr) {
cur^=i;
}
for(int i=0;i<=9;i++) {
cur^=i;
}
System.out.println(cur);
}
实现->不用判断和max函数求两个int类型的最大一个
public static int getMax(int a,int b) {
int c=a-b; //通过判断c是正数还是负数判断a是否大于b
c>>>=31; //保留最高位即可,最高位为1的话为负数,则a<b
return c*b+((c^1)*a);
}
但是此方法有个缺陷,那便是溢出问题,当然我们可以使用long类型解决,但是如何更优雅的使用只用int解决呢
什么时候相减的时候会发生溢出现象呢,当然是数值比较大,且号位相反,例如负数减正数,正数减负数,这样都会使得绝对值加大,导致溢出现象,但是翻过来思考,当号位相反,我们直接返回正数那个就行了,不需要相减判断,只需要判断谁是正数即可。
public static int getMax2(int a,int b) {
int bigger_zero=(a>>>31)^1; //判断a是否大于0
int notSame=(a>>>31)^(b>>>31),isSame=1^notSame;//判断a和b是否符号位相同
int smaller=((a-b)>>>31),bigger=smaller^1; //判断a-b结果是大于0还是小于0
int choosea=(isSame&bigger)|(notSame&bigger_zero);//只需要判断是否选择a即可,因为这是非a即b的选择
return choosea*a+(choosea^1)*b; //最后只会加一个值,非a即b
}
Brian Kernighan算法 求最右状态的1
给一个数例如二进制表示的为:01101000 应该返回的值是00001000
- 先取反得到10010111
- 加1得到10011000
- 再与原来的数进行与运行01101000&10011000=00001000
由于取反加1得到是相反数于是可以直接n&(-n)获取n的最右侧1
public static int getRight(int n){
return n&(-n);
}
找到唯二出现的数
题目:
给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
你必须设计并实现线性时间复杂度的算法且仅使用常量额外空间来解决此问题。
- 通过异或所有元素获得a^b的结果,cur=cur2=a ^ b
- 获取a异或b中的存在1的位置curr
- 再遍历一遍数组,与curr不为0的异或cur,否则异或cur2,相信学了上面的知识,你已经知道为什么这样做了
class Solution {
static public int cur=0;
public int[] singleNumber(int[] nums) {
cur=0;
for(int i:nums){
cur^=i;
}
int curr=cur&(-cur);
int cur2=cur;
for(int i:nums){
if((i&curr)==0)cur^=i;
else cur2^=i;
}
return new int[]{cur,cur2};
}
}
判断一个数是否为2的某次方
public boolean int isPow(int n){
return n==(n&-n);
}
int 类型判断一个数是否为3的某次方
public boolean int isPow(int n){
return n>0&&Math.pow(3,19)%n==0; //3的19次方为int类型最大3的次方
}
求大于一个数的最小2的某次幂
public int getmin(int n){
if(n<=0)return 1;
for(int i=1;i<=8;i*=2){
n|=(n>>>i);
}
return n+1;
}
快速求与&区间[left,right]的所有值
public int getres(int left,int right){
while(right>left){
right-=right&-right;
}
return right;
}
归并思想倒置二进制 10001100 -> 00110001
public static int reverseBit(int n){
n=((n&0xaaaaaaaa)>>>1) | ((n&55555555)<<1);
n=((n&0xcccccccc)>>>2) | ((n&0x33333333)<<2);
n=((n&0xf0f0f0f0)>>>4) | ((n&0x0f0f0f0f)<<4);
n=((n&0xff00ff00)>>>8) | ((n&0x00ff00ff)<<8);
n=(n>>>16)|(n<<16);
return n;
}
快速获取二进制数里有几个1
public static int getone(int n){
n=(n&0x55555555)+((n>>>1)&0x55555555);
n=(n&0x33333333)+((n>>>2)&0x33333333);
n=(n&0x0f0f0f0f)+((n>>>4)&0x0f0f0f0f);
n=(n&0x00ff00ff)+((n>>>8)&0x00ff00ff);
n=(n&0000ffff)+((n>>>16)&0xffff0000);
return n;
}