OptimalSolution(8)--位运算
一、不用额外变量交换两个整数的值
如果给定整数a和b,用以下三行代码即可交换a和b的值。a = a ^ b; b = a ^ b; a = a ^ b;
a = a ^ b :假设a异或b的结果记为c,c就是a整数位信息和b整数位信息的所有不同信息。例如,a=4=100,b=3=011,a^b=c=000
b = a ^ b :a异或c的结果就是b。比如a=4=100,c=000,a^c=011=3=b,也就是b = a ^ b ^ b = a
a = a ^ b :b异或c的结果就是a。比如b=3=011,c=000,b^c=100=4=a,也就是a = a ^ b ^ a =
二、不用任何比较判断找出两个数中较大的数
问题:给定两个32位整数a和b,返回a和b中较大的。
1.得到a-b的值的符号,如果a-b的值出现溢出,返回结果就不正确
sign函数返回整数n的符号,整数和0返回1,负数返回0。如果a-b的结果为0或整数,那么scA=1,scB=0,return a ;如果a-b的值为负数,那么scA=0,scB=1,return b;
public int flip(int n){
return n ^ 1;
} public int sign(int n){
return flip((n>>31)&1);
} public int getMax1(int a, int b){
int c = a - b;
int scA = sign(c);
int scB = flip(scA);
return a * scA + b * scB;
}
2.彻底解决溢出的问题
情况1:如果a和b的符号不同(disSab == 1,sameSab==0),则有
如果a为0或正,b为负(sa == 1,sb == 0),那么returnA与sc无关,为sa==1,returnB=0,返回a
如果a为负,b为0或正(sa==0,sb==1),那么returnA==0,returnB=1,返回b
情况2:如果a和b的符号相同(difSab==0,sameSab=1),那么此时a-b的值绝对不会溢出:
如果a-b为0或正(sc==1),那么returnA=sc=1,returnB=0,返回a
如果a-b为负(sc==0),那么returnA=0,returnB=1,返回b
public int getMax2(int a, int b){
int c = a - b;
int sa = sign(a);
int sb = sign(b);
int sc = sign(c);
int difSab = sa ^ sb;
int sameSab = flip(difSab);
int returnA = difSab * sa + sameSab * sc;
int returnB = fiip(returnA);
return a * returnA + b * return B;
}
三、整数的二进制表达式中有多少个1
问题:给定一个32位整数n,可为0,可为正,可为负,返回该整数二进制表达式中1的个数
1.整数n每次进行无符号右移(>>>)一位,检查最右边的bit是否为1。需要经过32次循环
public int count1(int n){
int res = 0;
while(n!=0){
res += n & 1;
n >>> = 1;
}
}
2.循环次数只和1的个数有关的解法。每进行一次n &= (n-1);操作,接下来在while循环中就可以忽略掉bit位上为0的部分。
例如,n=01000100,n-1=01000011,n&(n-1)=01000000,res=1,然后,n=01000000,n-1=00111111,n&(n-1)=00000000,res=2,结束。
因此,n&(n-1)操作实际上是抹掉n最右边的那一个1。
public int count2(int n){
int res = 0;
while(n != 0){
n &= (n-1);
res++;
}
return res;
}
3.同方法2,只不过是将n&(n-1)操作改成n -= n & (~n+1),也是移除最右侧的1的过程。n & (~n+1)是得到n中最右侧的1
例如:n=01000100,~n=10111011,~n+1=10111100,n & (~n+1) = 00000100,n - n & (~n+1) = 01000100,同理。
四、在其他数都出现偶数次的数组中找到出现奇数次的数
问题一:只有一个数出现了奇数次,其他的数都出现了偶数次
public void printOddTmesNum1(int[] arr){
int eO = 0;
for(int cur : arr){
eO ^= cur;
}
System.out.println(eO);
}
问题二:有两个数出现了奇数次,其他的数出现了偶数次
主要关注:int rightOne = eO & (~eO + 1);这个操作是得到eO最右边的1表示的数,例如01000100经过操作后变成00000100
public void printOddTimesNum2(int[] arr){
int eO = 0, eOhasOne = 0;
for(int curNum : arr){
eO ^= curNum;
}
int rightOne = eO & (~eO + 1);
for(int cur : arr){
if((cur & right) != 0){
eOhasOne ^= cur;
}
}
System.out.println(eOhasOne + " " + (eO ^ eOhasOne));
}
五、在其他数都出现k次的数组中找到只出现一次的数
问题:给定一个整型数组arr和一个大于1的整数k,已知arr中只有1个数出现了1次,其他的数都出现了k次,返回只出现1次的数
两个七进制的数,忽略进位相加:
a : 6 4 3 2 6 0 1
b : 3 4 5 0 1 1 1
c : 2 1 1 2 0 1 2
思路:上面的计算中,第i位上无进位相加的结果就是c[i] = (a[i] + b[i])%7。同理,k进制的两个数a和b,在第i位上相加的结果就是c[i] = (a[i] + b[i])%k。那么,如果k个相同的k进制数进行无进位相加,根据c[i] = (k * a[i] + k * b[i])%k,可知,相加的结果一定是每一位上都是0的k进制数。
解法:设置一个变量eO,它是一个32位的k进制数,且每个位置上都是0。然后遍历arr,把遍历到的每一个整数都转换为k进制数,然后与e0进行无进位相加。遍历结束后,把32位的k进制数eORes转换成十进制就是要求的结果。
函数1:将十进制的数转换成32位k进制的数组
public int[] getKSysNumFromNum(int value, int k){
int[] res = new int[32];
int index = 0;
while(value != 0){
res[index++] = value % k;
value = value / k;
}
return res;
}
函数2:将表示k进制的数组转换成十进制的数
public int getNumFromKSysNum(int[] eO, int k){
int res = 0;
for(int i = eO.length - 1 ; i != -1; i--){
res = res * k + eO[i];
}
return res;
}
函数3:将十进制的value转换成curKSysNum数组表示的32位k进制数后无进位地加到eO数组的每一位上
public void setExclusiveOf(int[] eO, int value, int k){
int[] curKSysNum = getKSysNumFromNum(value, k);
for(int i = 0; i != eO.length; i++){
eO[i] = (eO[i] + curKSysNum[i]) % k;
}
}
函数4,将arr中所有的数转换成32位k进制后加到eO变量的每一位上,然后将eO变量转换成十进制的数并返回
public int onceNum(int[] arr, int k){
int[] eO = new int[32];
for(int i = 0; i != arr.length; i++){
setExclusiveOr(eO, arr[i], k);
}
int res = getNumFromKSysNum(eO, k);
return res;
}
六、只用位运算不用算术运算实现整数的加减乘除运算
题目:给定两个32位整数a和b,可正,可负,可0。不能使用算术运算符,分别实现a和b的加减乘除运算。如果给定的a和b执行加减乘除的某些结果本来就会导致数据的溢出,那么不用为那些结果负责。
1.用位运算实现加法运算
注意:初始化sum=a,是为了考虑当b为0时,无法进入while循环执行sum = a ^ b;这个操作。
public int add(int a, int b){
int sum = a;
while( b != 0){
sum = a ^ b;
b = (a & b) << 1;
a = sum;
}
return sum;
}
分析实现过程:
1.如果不考虑进位,a^b就是正确结果,因为1加1=0,1加0=1,0加1=1,0+0=0
例如:
a:0 0 1 0 1 0 1 0 1
b:0 0 0 1 0 1 1 1 1
c:0 0 1 1 1 1 0 1 0
2.在只算进位的情况下,也就是a加b过程中由于进位产生的值是什么,就是(a&b)<<1,因为在第i位上只有1和1相加才会产生上一位即i-1位的进位
a:0 0 1 0 1 0 1 0 1
b:0 0 0 1 0 1 1 1 1
d:0 0 0 0 0 1 0 1 0(从右数第1位和第3位需要进位,因此在相加的过程中,第2位和第4位上需要加上1,因此(a&b)<<1)
3.把第1步的不考虑进位的相加值与第2步的只考虑进位的产生值再相加,就是最终的结果。由于过程中可能还会产生进位,所以需要重复直到进位产生的值完全消失。
a:0 0 1 0 1 0 1 0 1
b:0 0 0 1 0 1 1 1 1
c:0 0 1 1 1 1 0 1 0
d:0 0 0 0 0 1 0 1 0 c:0 0 1 1 1 0 0 0 0
d:0 0 0 0 1 0 1 0 0 c:0 0 1 1 0 0 1 0 0
d:0 0 0 1 0 0 0 0 0 c:0 0 1 0 0 0 1 0 0
d:0 0 1 0 0 0 0 0 0 c:0 0 0 0 0 0 1 0 0
d:0 1 0 0 0 0 0 0 0 c:0 1 0 0 0 0 1 0 0(返回)
d:0 0 0 0 0 0 0 0 0
2.用位运算实现减法运算
实现a-b,只要实现a+(-b)即可。一个数的相反数,就是这个数的二进制数表达取反加1(补码)。
public int negNum(int n){
return add(~n, 1);
} public int minus(int a, int b){
return add(a, negNum(b));
}
3.用位运算实现乘法运算
a*b=a * 20 * b0 + a * 21 * b1 + a * 22 * b2 + ... + a * 231 * b31(bi表示的是二进制中第i位的值,从左起0开始)
public int multi(int a, int b){
int res = 0;
while(b != 0){
if((b & 1) != 0){
res = add(res, a);
}
a <<= 1;
b >>> = 1;
return res;
}
分析执行过程:
假设a=22=000010110,b=13=000001101,res=0
a:0 0 0 0 1 0 1 1 0
b:0 0 0 0 0 1 1 0 1
r:0 0 0 0 0 0 0 0 0
b的最右侧是1,所以res = res + a,同时b右移一位,a左移一位
a:0 0 0 1 0 1 1 0 0
b:0 0 0 0 0 0 1 1 0
r:0 0 0 0 1 0 1 1 0
b的最右侧是0,res不变,同时b右移一位,a左移一位
a:0 0 1 0 1 1 0 0 0
b:0 0 0 0 0 0 0 1 1
r:0 0 0 0 1 0 1 1 0
b的最右侧是1,res = res + a,同时b右移一位,a左移一位
a:0 1 0 1 1 0 0 0 0
b:0 0 0 0 0 0 0 0 1
r:0 0 1 1 0 1 1 1 0
b的最右侧是1,res = res + a,同时b右移一位,a左移一位
a:1 0 1 1 0 0 0 0 0
b:0 0 0 0 0 0 0 0 0
r:1 0 0 0 1 1 1 1 0
b为0,返回res=100011110=286
4.用位运算实现除法运算
用位运算实现除法运算,其实就是乘法的逆运算。
(1)a和b都不为负数或者如果a和b中有一个负数或者都为负数时,可以先把a和b转成正数,计算完成后再看res的真实符号即可(正负得负、负负得正、正正得正)。
public boolean isNeg(int n){
return n < 0;
} public int div(int a, int b){
int x = isNeg(a) ? negNum(a) : a;
int y = isNeg(b) ? negNum(b) : b;
int res = 0;
for(int i = 31; i > -1; i = minus(i,1){
if(x >= (y << i)){
res |= (1<<i);
x = minus(x, y<<i);
}
}
return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
}
如果b*res=a,那么a=b * 20 * res0 + b * 21 * res1 + b * 22 * res2 + ... + b * 231 * res31
分析执行过程:让b向左移动i次,即b * 2i,然后观察a是否b * 2i,如果大于,就令res的第i位等于1,然后让a - b * 2i为a,然后反复操作。
假设a=286=100011110,b=22=000010110,res=0
a:1 0 0 0 1 1 1 1 0
b:0 0 0 0 1 0 1 1 0
r:0 0 0 0 0 0 0 0 0
(i=3时)a = a - b * 2
3
a:0 0 1 1 0 1 1 1 0
b:0 0 0 0 1 0 1 1 0
r:0 0 0 0 0 1 0 0 0
(i=2时)a = a - b * 2
2
(i=2时)
a:0 0 0 0 1 0 1 1 0
b:0 0 0 0 1 0 1 1 0
r:0 0 0 0 0 1 1 0 0
(i=1时)b向左移动一位后大于a,说明a已经不能包含b * 2
1
,
a:0 0 0 0 1 0 1 1 0
b:0 0 0 0 1 0 1 1 0
r:0 0 0 0 0 1 1 0 1
(i=0时)b向左移动一位后a==b,说明剩下的a还能包含一个b * 2
0
,即res0=1,此时说明a已经被完全分解干净,返回res=000001101=13
(2)以上方法可以算绝大多数情况,但是int类型的整数最小值为-2147483648,最大值为2147483647,最小值的绝对值比最大值的绝对值大1,所以,如果a或b等于最小值,是转不成相对应的正数的(~n + 1)。
即有下面四种情况:
- 如果a和b都不为最小值,直接使用div(a,b)
- 如果a和b都为最小值,直接返回1
- 如果a不为最小值,而b为最小值,直接返回0
- 如果a为最小值,b不为最小值,怎么办?
假设整数的最大值为9,最小值为-10,当a和b都属于[-9,9]时,也就是情况1;当a和b都等于-10时,也就是情况2;当a属于[-9,9],而b等于-10时,也就是情况3;
那么,当a=-10,而b属于[-9,9]时,
第一步:假设a=-10,b=5
第二步:计算(a+1)/b的结果,记为c,即c=-9/5=-1
第三步:计算c*b的结果,即-1*5=-5
第四步:计算(a - (c * b))/b,记为rest,意义是修正值,即(-10 - (-5))/5=-1,得到的是修正值,即rest=-1
第五步:返回c+rest,即-9
即a/b的值可以表示为
综上,除法运算的全部过程为:(注意要有异常处理的过程。)
public int divide(int a, int b){
if(b==0){
throw new RuntimeException("divided is 0");
}
if(a == Integer.MIN_VALUE && b == Integer.MIN_VALUE){
return 1;
} else if(b == Integer.MIN_VALUE){
return 0;
} else if(a == Integer.MIN_VALUE){
int res = div(add(a,1),b);
return add(res, div(minus(res, b)), b));
} else{
return div(a, b);
}
}
七、O(n)时间复杂度得到输入数组中某两个数异或的最大值。
例如:[3, 10, 5, 25, 2, 8]中,5^25的最大值是28
思路:比特位操作。
解法:
生成变量max,表示
生成变量mask,表示,
XOR性质,A^B=C → A^B^B=C^B → A=C^B 则tmp ^ prefix = max →
3 → 0 0 0 1 1
10 → 0 1 0 1 0
5 → 0 0 1 0 1
25 → 1 1 0 0 1
2 → 0 0 0 1 0
8 → 0 1 0 0 0 i=4时,mask=10000, set={00000,10000},tmp=10000,prefix=00000, max=10000
i=3时,mask=11000, set={00000,01000,11000},tmp=11000,prefix=00000,max=11000
i=2时,mask=11100, set={00000,01000,00100,11000},tmp=11100,prefix=00100,max=11100
i=1时,mask=11110, set={00010,01010,00100,11000,01000},tmp=11110,set中不包含
i=0时,mask=11111, set={00011,01010,00101,11001,00010,01000},tmp=11111,set不包含
OptimalSolution(8)--位运算的更多相关文章
- Java 位运算2-LeetCode 201 Bitwise AND of Numbers Range
在Java位运算总结-leetcode题目博文中总结了Java提供的按位运算操作符,今天又碰到LeetCode中一道按位操作的题目 Given a range [m, n] where 0 <= ...
- 简简单单学会C#位运算
一.理解位运算 要学会位运算,首先要清楚什么是位运算?程序中的所有内容在计算机内存中都是以二进制的形式储存的(即:0或1),位运算就是直接对在内存中的二进制数的每位进行运算操作 二.理解数字进制 上面 ...
- SQL Server时间粒度系列----第8节位运算以及设置日历数据表节假日标志详解
本文目录列表: 1.位运算 2.设置日历数据表节假日标志 3.总结语 4.参考清单列表 位运算 SQL Server支持的按位运算符有三个,分别为:按位与(&).按位或(|).按位异或 ...
- js中的位运算
按位运算符是把操作数看作一系列单独的位,而不是一个数字值.所以在这之前,不得不提到什么是"位": 数值或字符在内存内都是被存储为0和 1的序列,每个0和1被称之为1个位,比如说10 ...
- Java中的位运算
昨天去面试的时候做到了一道Java的位运算题目,发现有个运算符不懂:">>>",今天特地查了一下,并小结一下常见的位运算符号: ~ 按位非(NOT)(一元运算) ...
- C#位运算讲解与示例
首先每一个权限数都是2的N次方数 如:k1=2 ; //添加 k2=4 ; //删除 k3=8; //修改 ... 如此定义功能权限数,当需要组合权限时,就需要对各个所拥有的权限数按位或了. 如: p ...
- C#枚举中的位运算权限分配浅谈
常用的位运算主要有与(&), 或(|)和非(~), 比如: 1 & 0 = 0, 1 | 0 = 1, ~1 = 0 在设计权限时, 我们可以把权限管理操作转换为C#位运算来处理. 第 ...
- Java位运算经典实例
一 源码.反码.补码 正数的源码.反码.补码相同,例如5: 5的源码:101 5的反码:101 5的补码:101 负数的源码.反码.补 ...
- C入门---位运算
程序中的所有数在计算机内存中都是以二进制的形式储存的.位运算直接对整数在内存中的二进制位进行操作.由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快. (1),与(&)运算 ...
随机推荐
- HashMap和Hashtable的联系和区别
实现原理相同,功能相同,底层都是哈希表结构,查询速度快,在很多情况下可以互用,早期的版本一般都是安全的. HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分 ...
- Maven 梳理-自动创建Maven项目(非web)
mvn archetype:create和mvn archetype:generate create is deprecated in maven 3.0.5 and beyond,在maven3.0 ...
- VMware CentOS7 安装
一.软硬件准备 作者:小啊博 QQ:762641008 转载请声明:https://www.cnblogs.com/-bobo 1.准备Centos7镜像 软件:推荐使用VMwear,我用的是VMwe ...
- idea设置方法或常量字段上浮显示对应的注释内容
1.进入idea中,如下图进入设置 如图勾选并设置显示延迟时间 打开文件进行鼠标移动测试 可以看到鼠标移动到字段常量或方法上时显示了对应的注释类容
- Django之使用celery和NGINX生成静态页面实现性能优化
性能优化原理: 当我们要给client浏览器返回一个页面时,我们需要去数据库查询数据并将数据和基本页面模板渲染形成页面返回给客户端,但如果每一个用户访问时都去查询一次首页的的数据时,当日访问量很大时那 ...
- setfacl、getfacl
当用户访问一个文件时,权限匹配的顺序为owner--->group--->other.当设置访问控制列表后,owner--->facl_user--->group---> ...
- Docker下配置nacos
前言 近段时间在学dubbo,dubbo-admin死活装不上,无论是本地还是docker,所以把目光投向了其他配置中心,我选定的是阿里新开源的nacos. 正文 拉取镜像到本地docker dock ...
- MySQL 数据库删除表中重复数据
采集数据的时候,由于先期对页面结构的分析不完善,导致采漏了一部分数据.完善代码之后重新运行 Scrapy,又采集了一些重复的数据,搜了下删除重复数据的方法. N.B. 删除数据表的重复数据时,请先备份 ...
- mpvue 图片上传预览组件封装
<template> <div class="j-pic-upload"> <div class="j-upload-btn" @ ...
- cocos2d-x 3.2锚点,Point,addchild,getcontensize
一,锚点 打个比方.在墙挂一幅画时,要钉一个钉子,那个钉子就是锚点. 然后挂图时,钉子(锚点)放在要订的位置(position),订下去.完成(贴图结束). 贴图的基本点,锚点默认为(0.5,0.5) ...