编写一个方法,找出两个数字a和b中最大的那一个。
不得使用if-else或其他比较运算符。
示例:
输入: a = 1, b = 2
输出: 2
条件运算符是具有短路特性的,当前面条件不满足时,后面就不会继续运算。所有可以使用短路特性来代替if
判断条件。
判断两个数的大小关系,先作差,然后判断最高位的符号的正负即可。位运算要考虑溢出的情况,所以需要考虑同号和异号两种情况。
int maximum(int a, int b) {
int res = a;
//异号处理
(a ^ b) >> 31 && (a >> 31) && (res = b);
//同号处理
(-(a ^ b) >> 31) && ((a - b) >> 31) && (res = b);
return res;
}
首先我们目标是得到一个标志ans,当a大的时候ans为1,b大的时候ans为0。如果我们能得到ans,则最终的答案是 ans * a + (ans ^ 1) * b
;这里异或1的操作,把0变成1,把1变成0。
如何得到ans,容易想到的是拿到b-a的符号位,但整数溢出会导致符号位不可靠。所以我们还需要拿到a和b的符号位,如果a,b同号,则相减不会溢出,如果异号,则直接选择非负的那个(即符号位为0)的即可。注:32位整型的符号位可以通过将原数右移31位得到。
以下记a的符号位为x,b的符号位为y,b-a的符号位为z。
得到下列真值表:
x | y | z | ans |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 |
0 | 1 | 0 | 1 |
0 | 1 | 1 | 1 |
1 | 0 | 0 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 0 |
1 | 1 | 1 | 1 |
真值表大家可以逐行检验,然后使用卡诺图求出ans的表达式。
根据从真值表求表达式,以及卡诺图相关知识,可以得到:
ans = (!x & y) | (!x & z) | (y & z);
注:这里!代表逻辑非,代码实现中应当使用(x ^ 1)
代替比较好。
因为只用到了非x,以下代码中的x直接就是这里的(x ^ 1)
。
int maximum(int a, int b) {
unsigned aa = a, bb = b;
bool x = (aa >> 31) ^ 1, y = bb >> 31, z = (bb - aa) >> 31;
bool ans = (x & y) | (x & z) | (y & z);
return ans * a + (ans ^ 1) * b;
}
另外一种方法:
int
计算溢出的情况可以转化为long long
计算。实现如下:
int maximum(int a, int b) {
long k = (((long)a - (long)b) >> 63) & 1;
return b * k + a * (k ^ 1);
}
最好把输入的a和b强行转成无符号整型再计算,否则leetcode的编译器不让做越界的减法运算。
利用数学上的公式:
max(a, b) = ((a + b) + abs(a - b)) / 2
为了回避abs,利用位运算实现绝对值功能:
也可以考虑先平方,再开方来求得绝对值。但是存在大数超出表示范围的情况,可能需要转成double值运算,或者使用数组来做大数乘法。
abs(var) = (var ^ (var >> 63)) - (var >> 63)
或者
long absolute(long a) {
int flag = a >> 63; //正数flag = 0,负数flag = -1
return (flag ^ a) - flag; //任何数与0异或值不变,任何数与-1异或等价于按位取反
}
代码实现:
class Solution {
public:
int maximum(int a, int b) {
long _sum = long(a) + long(b);
long _diff = long(a) - long(b);
long _abs_diff = (_diff ^ (_diff >> 63)) - (_diff >> 63);
return (_sum + _abs_diff) / 2;
}
};
或者理解为:
max(a, b)的本质是补齐ab之间的相对距离
class Solution:
def maximum(self, a: int, b: int) -> int:
return int(((a + b) + abs(a - b)) / 2)
原文:https://www.cnblogs.com/shijiashuai/p/14417628.html