题目一、位运算求较大值
给定两个有符号32位整数a和b,返回a和b中较大的。
【要求】不用做任何比较判断。
解题思路
- 不能通过
<
,>
比较,则我们可以计算a - b
的值,然后判断结果的符号得知 a 和 b 哪个大 - 判断一个数是正数还是负数,只需判断它的符号即可,对于
int
类型,它的符号位就是二进制首位,也就是说,将一个int
类型的数右移31位,就可以在最低位得到它的符号。 - 上边通过判断
a - b
的符号来判断a与b孰大孰小的方法是有问题的,会有a - b
溢出的情况出现,但这种情况只存在于a和b符号不相同的情况,因此我们可以进一步判断:- 什么情况下a大于b?
- a与b符号相同,且
a - b > 0
- a与b符号不同,且
a > 0
- a与b符号相同,且
- 什么情况下a小于b?
- a不大于b的情况下😏,其实就是上面情况的反面
- 什么情况下a大于b?
public class GetMax {
/**
* 符号反转
* 请保证参数n,不是1就是0
*/
public static int flip(int n) {
return n ^ 1;
}
/**
* n是非负数,返回1
* n是负数,返回0
*/
public static int sign(int n) {
return (n >> 31) & 1;
}
/**
* 大小比较方法一,a - b 可能会溢出
*/
public static int getMax1(int a, int b) {
int c = a - b;
// a - b 为非负,scA为1;a - b 为负,scA为0
int scA = sign(c);
// scA为0,scB为1;scA为1,scB为0
int scB = flip(scA);
// c > 0,返回a;c < 0,返回b
return a * scA + b * scB;
}
/**
* 大小比较方法二
*/
public static 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);
// 定义返回A的条件:1. a和b符号相同,c大于0;2. a和b符号不同,a大于0;
int returnA = difSab * sc + sameSab * sa;
int returnB = flip(returnA);
return a * returnA + b * returnB;
}
}
题目二、判断一个32位正数是不是2的幂、4的幂
解题思路:
- 首先要知道如果一个数是2的幂,那它应该有什么特点?二进制只有一个位上是1,其余都是0。
- 如果一个数是4的幂,那它应该有什么特点?首先它一定是2的幂,所以它的二进制数也只有一个位上是1,其余都是0;其次,1的位置也是有讲究的,只能是隔位出现,即1可能出现的位置为
01010101...0101
- 如何判断一个数是2的幂:让该数减去1,就会得到一个类似
00...0011...11
的数,显然该数与原数相与&
就会得到0。 - 如何判断一个数是4的幂:首先判断它是否是2的幂,如果是,则接着判断,二进制的1是否出现在了正确的位置上,就将该数与
10101010...1010
相与,如果结果为0,则证明它是4的幂。
public class Power {
public static boolean is2Power(int n) {
return (n & (n - 1)) == 0;
}
public static boolean is4Power(int n) {
// 0xaaaaaaaa -> ...0101010
return (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0;
}
public static void main(String[] args) {
System.out.println(is4Power(4));
}
}
题目三、实现32位整数的加减乘除运算
题目描述:给定两个有符号32位整数a和b,不能使用算术运算符,分别实现a和b的加、减、乘、除运算
【要求】
如果给定a、b执行加减乘除的运算结果就会导致数据的溢出,那么你实现的函数不必对此负责,除此之外请保证计算过程不发生溢出。
解题思路:
-
加法:首先对于异或运算,
a ^ b
得到的是a和b无进位相加的结果(比如01101 ^ 00111 = 01010
),而a & b
得到的是两个数的进位信息(01101 & 00111 = 00101
,等于1表示该位置上的数需要进位),而(a & b) >> 1
和a ^ b
相加即为a + b
的结果,所以又回到了两数相加的情况,就这样一直循环,直到进位信息为0,表示不需要进位了,此时返回异或后的结果即可。public static int add(int a, int b) { int sum = a; while (b != 0) { sum = a ^ b; b = (a & b) << 1; a = sum; } return sum; }
-
减法:数a与数b相减,可以看成数a与数b的**补码**相加
/** * 求补码 */ public static int complement(int n) { return add(~n, 1); } /** * 减法 */ public static int minus(int a, int b) { return add(a, complement(b)); }
-
乘法:有点类似计组里面的原码一位乘,假设a为被乘数,b为乘数,我们可以看成是b个a相加,但是是按照我们平常竖式运算的方式相加:
- 根据b最低位是0还是1,来决定sum加0还是加a
- 由于每次相加都是错位相加,所以在相加前,需要将a左移一位(不考虑溢出的情况)
- 相加完,把b右移一位,以便取到下一位
public static 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是被除数,b是除数,则我们首先需要进行一个对位的过程,也就是把b左移到恰好不超过a的位置:
通过对位,我们可以得出最高位的1在哪个位置,我们利用位运算也能实现这个过程
- 首先我们需要计算出b左移多少位,才能恰好不超过a;由于b左移可能会出现溢出,所以我们通过让a右移来计算需要移动的位数n
- 计算出需要移动的位数n后,就知道最高位的1,在哪个位置,左移n位之后加到res中
- 然后需要把a减去b左移n位后的结果赋值给a,重复上述过程
// 判断是否为负数
public static boolean isNeg(int n) {
return n < 0;
}
// 取相反数
public static int negNum(int n) {
return -n;
}
// 除法运算
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;
}