「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」
前言
笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。
系列文章收录《算法》专栏中。
力扣上目前没有此题,是笔者自己的扩展。
问题描述
通过位运算做整数乘法,求两个整数的乘积,要求完全使用“&“、“|”、“^”、“~”、“<<”、“>>”位运算。
示例:
输入: a = 2, b = 2
输出: 4
提示:
a,b均可能是负数或 0- 结果不会溢出 32 位整数
确定学习目标
对《通过位运算做整数乘法》的算法过程进行剖析。
剖析
原始十进制分析
这个算法也是没有学习成本的,除了简单的位运算没有其他新知识。可以先想下我们平时进行十进制乘法运算的时候是怎么处理的,比如53*15:
- 先用5*53=265,先记下暂时结果为265。
- 再用1*53=53,这时候因为1是在十位上所以53需要往右移动一位,其实就当于530,把前面的暂时结果265和530相加为795得到新的暂时结果。
- 最后发现乘数15没有位可以和乘数进行相乘了,那么就停止,暂时结果直接作为正式结果返回。
下面我们总结成通用的步骤
总结
- 从乘数的个位开始,去乘以被乘数,把乘积作为暂时结果。
- 把乘数往右移动一位(舍去低位上面一步已经拿到了暂时结果),去乘以被乘数,把乘积往右移动一位(就相当于上面的53往右移动一位就是530)和第一步的暂时结果相加得到新的暂时结果。
- 重复步骤2,直到乘数右移没有位可以舍弃,那么就停止,暂时结果直接作为正式结果返回。
上面是正常情况下的乘法步骤。如果乘数和被乘数有一个存在0就直接返回结果0。如果存在一个是负数就先用正数进行,最后结果取反。如果两个都是负数,那么直接用对应的正数进行,最后结果不用取反。
二进制
如上图二进制的步骤和十进制类似,甚至更好的地方不用和b的各个位进行相乘直接对被乘数进行移位,加法参考我的《二进制加法》,我们直接给出总结:
- 从乘数的个位开始,把被乘数作为暂时结果。
- 把乘数往右移动一位(舍去低位上面一步已经拿到了暂时结果),把被乘数往右移动一位,如果乘数的最低位不为0那么把移动好的被乘数和第一步的暂时结果相加得到新的暂时结果。
- 重复步骤2,直到乘数右移没有位可以舍弃,那么就停止,暂时结果直接作为正式结果返回。
上面是正常情况下的乘法步骤。如果乘数和被乘数有一个存在0就直接返回结果0。如果存在一个是负数就先用正数进行,最后结果取反。如果两个都是负数,那么直接用对应的正数进行,最后结果不用取反。
下面直接上代码
代码
/**
* 1. a和b异或位运算得出每位抛去进位的结果。
* 2. a和b与位运算再左移1位,得出进位结果
* 3. 设进位结果为b,a为每位抛去进位的结果,如果b!=0就重复步骤1和2。
* <p>
* 溢出的话就是只表示允许表示的位的结果(比如int 直接从右往左取32位)
*
* @param a
* @param b
* @return
*/
public static int add(int a, int b) {
int sum = a;
while (b != 0) {
sum = a ^ b;
int carry = (a & b) << 1;
a = sum;
b = carry;
}
return sum;
}
/**
* 乘法:
* 1. 从乘数的个位开始,把被乘数作为暂时结果。
* 2. 把乘数往右移动一位(舍去低位上面一步已经拿到了暂时结果),把被乘数往右移动一位,如果乘数的最低位不为0那么把移动好的被乘数和第一步的暂时结果相加得到新的暂时结果。
* 3. 重复步骤2,直到乘数右移没有位可以舍弃,那么就停止,暂时结果直接作为正式结果返回。
* <p>
* 上面是正常情况下的乘法步骤。如果乘数和被乘数有一个存在0就直接返回结果0。如果存在一个是负数就先用正数进行,最后结果取反。如果两个都是负数,那么直接用对应的正数进行,最后结果不用取反。
*
* @param a 被乘数
* @param b 乘数
* @return
*/
public static int multiply(int a, int b) {
// 结果先设为0
int result = 0;
// a和b其中一个位0就直接返回0
if (a == 0 || b == 0) {
return result;
}
//结果先设为不用取反
boolean isNegate = false;
//如果a或者b位0就把isNegate置位true
if (a < 0 || b < 0) {
isNegate = true;
}
//如果a小于0就取反,我们最终需要对正数进行乘法运算
if (a < 0) {
a = -a;
}
//如果b小于0就取反,我们最终需要对正数进行乘法运算
if (b < 0) {
b = -b;
}
//这里需要判断b是否等于0,下面会对b进行右移舍弃低位,最终b会等于0停止更新"暂时结果"
while (b != 0) {
//只有b的最低位不等于0才对结果进行更新,不然a,b只移位
if ((b & 0x1) != 0) {
result = add(a, result);
}
//乘数向右移动,逐渐舍弃低位
b >>= 1;
// 被乘数需要向右边移动,配合乘数向右移动,需要进位
a <<= 1;
}
//结果需要取反就取反
if (isNegate) {
result = -result;
}
return result;
}