22.1.30 位运算
1.资源限制类题目的技巧:
1))哈希函数可以把数据按照种类均匀分流;
2)布隆过滤器用于集合的建立与查询,并可以节省大量空间;
3)一致性哈希解决数据服务器的负载管理问题;
4)利用并查集结构做岛问题的并行计算;
5)位图解决某一范围上数字的出现情况,并可以节省大量空间;
6)利用分段统计思想、并进一步节省大量空间;
7)利用堆、外排序来做多个处理单元的结果合并;
2.位运算
1)判断一个32位正数是否为2的幂或者4的幂;
-
2的幂:某一个数为2的幂的特点是这个数的二进制表示中只有一个1;
-
x&(x-1) == 0;
//成立则表示这个数的二进制表示中只有一个1;
-
-
4的幂:4的幂一定是2的幂,且4的幂的二进制表示上的1一定分布在0,2,4,6,8....... 位上;
-
x&(x-1) == 0;
//成立则表示这个数的二进制表示中只有一个1;
x&(0x55555555) != 0;
//0x55555555表示的二进制数为......010101;
-
2)返回a,b中较大的那个32位整数:
-
要求:不做任何比较
-
要点:“+”可以将互斥条件的if else写成数学形式;
-
code:
-
public static void main(String[] args)
{
System.out.println(getMax(-6, 5));
}
public static int sign(int x)
{
return flip((x >> 31) & 1);
}
//右移31位取出x的符号位;
//0正1负;
//经历flip函数后0表示负数,一表示正数;
public static int flip(int n)
{
return n ^ 1;
}
//保证n的输入只能是1或者0;
//输入为1,输出为0;
//输入为0,输出为1;
public static int getMax(int a, int b)
{
int c = a - b;
int sa = sign(a);
int sb = sign(b);
int sc = sign(c);
//取得a,b,c的符号位
int difSab = sa ^ sb;
//判断a,b的符号情况
int sameSab = flip(difSab);
int returnA = difSab * sa + sameSab * sc;
//返回a有两种情况(+可以理解为或,*理解为且);
//a,b的符号位不同,a为正时返回a;
//a,b的符号位相同,c的符号位为正是返回a;
int returnB = flip(returnA);
//不返回a就返回b
return a * returnA + b * returnB;
//“+”可以将互斥条件的if else写成数学形式;
}
3)位运算实现四则运算:
-
要求:给定两个有符号位的32位整数a,b,不使用简单运算符,实现加减乘除;
-
加法(假设给定a=13:01101和b=7:00111,实现13和7的相加):
-
过程模拟:
-
先将两个数进行异或(无进位相加),得到01010;
-
再将两个数进行&运算并将结果左移一位得到01010;
-
将第一步和第二步得到的数分别更新为a,b,重复前两步直到无进位信息,即第二部的运算结果全为0,此时第一步的结果就是a+b的结果;
-
-
code:
-
public static void main(String[] args)
{
System.out.println(getSum(13,7));
}
public static int getSum(int a,int b)
{
int sum = 0;
while(b!=0)
{
sum = a^b;
b = (a&b)<<1;
a = sum;
}
return sum;
}
-
-
-
减法(假设给定a=13:01101和b=7:00111,实现13和7的相减)
-
思路:13-7相当于13+(-7),求得7的相反数,再利用加法进行相加即可
-
code:
-
public static void main(String[] args)
{
System.out.println(getSum(13,negB(7)));
}
public static int getSum(int a,int b)
{
int sum = 0;
while(b!=0)
{
sum = a^b;
b = (a&b)<<1;
a = sum;
}
return sum;
}
public static int negB(int b)
{
return getSum(~b,1);
}
//得到B的相反数,~:补码的符号
-
-
-
乘法:
-
思路:二进制乘法类似与我们使用的竖式乘法,将第二个二进制数的每一位都与第一个数的每一位相乘展开,最后求进位和;
-
过程模拟:(给定数a =26= 011010,b =22= 010110)
-
0 1 1 0 1 0
0 1 0 1 1 0
* ------------------------------------------
0 0 0 0 0 0
0 1 1 0 1 0
0 1 1 0 1 0
0 0 0 0 0 0
0 1 1 0 1 0
0 0 0 0 0 0
------------------------------------------
0 1 0 0 0 1 1 1 1 0 0
结果所得到的二进制数转换为十进制为572 -
code:
-
public static void main(String[] args)
{
System.out.println(multi(13,7));
}
public static int multi(int a,int b)
{
int res = 0;
while(b!=0)
{
if((b&1)!=0)
{
res = getSum(res,a);
a<<=1;
b>>>=1;
}
}
return res;
}
public static int getSum(int a,int b)
{
int sum = 0;
while(b!=0)
{
sum = a^b;
b = (a&b)<<1;
a = sum;
}
return sum;
}
-
-
-
除法:
-
过程模拟:(假设给定0111100/00101)
-
除法相当于乘法的逆过程;
-
将0000101尽量左移到大小最接近0111100的程度,但是不能超过0111100.则第一次移动可以知道是左移3位得到0101000,根据上述乘法的过程模拟能知道结果的2^3位上有1;
-
-
继续重复上述过程:把0000101左移两位得到0010100,同理2^2位上有1;
-
此时0010100-0010100=0,过程结束,结果就为01100(二的二、三次方位上有一,其余都是零);
-
-
上述过程是我们脑海中的乘法逆过程的原理,在实际过程中左移可能导致溢出或者改变符号位,因此在实际过程中通常采用右移被除数达到接近除数的过程。
-
code:
-
public static boolean isNeg(int n) {
return n < 0;
}
public static int negNum(int n) {
return add(~n, 1);
}
public static 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 >> i) >= y) {
res |= (1 << i);
x = minus(x, y << i);
}
}
return isNeg(a) ^ isNeg(b) ? negNum(res) : res;
}
public static int divide(int a, int b) {
if (b == 0) {
throw new RuntimeException("divisor 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);
-
-
-