只使用位运算实现加减乘除

news/2024/10/17 16:21:28/

在线OJ: LeetCode 29. 两数相除

原题目的要求是不能使用乘法, 除法和取余运算符实现除法.

在本篇博客中把题目要求提高一点, 这里只使用位运算来实现, 顺便的也就把只使用位运算实现加减乘除实现了.

img

1 . 实现加法

首先我们需要知道两数之和可以是两个数位相加和不进位相加之和, 而两数进行异或(^)运算其实就是两个数对应二进制值的无进位相加.

我们可以把思路可以转换一下, 把加法用异或替换, 如果我们能够得到两个数相加的二进制进位信息为0, 那么此时的无进位结果就是两数相加的最终结果了.

只有二进制无进位信息, 相加的结果; 然后把这个结果加上进位信息, 就是两个数相加的最终结果.

抽象一下:

我们要计算 a + b

先算 a ^ b = a' 得到的结果就是无进位相加的结果, 然后我们再得到 a 和 b 相加的进位信息 b’, 那么此时 a + b = a' + b' 就是两数之和, 但我们这里是不能用加号, 所以我们需要逐个把进位信息再继续叠加 (即让a’ 和 b’ 再继续运算, 得到进位信息和不进位信息…), 直到进位信息为 0 结束.

那么进位信息如何计算?

只有当 a 和 b 的二进制对应位置上都是1, 才会产生进位, 只需要 通过 (a & b) << 1 就可得到进位结果.

所以, 只使用位运算实现加法的代码如下:

// 不使用算数运算实现两数相加
public static int add (int a, int b) {// 两个数的和等于两个数不进位相加和进位相加的和// a ^ b 得到的就是两数不进位相加的和// (a & b) << 1 得到的就是两数只相加进位的值// 一直循环至进位值为0计算结束int sum = a;while (b != 0) {sum = a ^ b;b = (a & b) << 1;a = sum;}return sum;
}

2 . 实现减法

两数相减, 等价于一个数加上另一个数的相反数, 即 a - b = a + (-b), 但这里是不能不能出现减号的, 所以, 可以用加法来模拟一个数的相反数, 因为 x 的相反数等于 x 取反再加 1 (~x + 1), 即 add(~x, 1).

所以, 只使用位运算实现减法可以在加法代码的基础上进行:

// 计算一个数字的相反数
public static int oppNum (int num) {return add(~num, 1);
}// 不使用算数运算实现两数相减
public static int minus(int a, int b) {// a - b 就相当于 a + (-b)// 一个数的相反数等于这个数取反再加1return add(a, oppNum(b));
}

3 . 实现乘法

看下面计算两个十进制数的乘法用的方法是不是很熟悉, 以 a = 12, b = 22 为例, a * b 通过如下方式计算;

  19
x 22
------3838
------418

这样的方法也适用于二进制, 19 的二进制是 10011, 22 的二进制是 10110, 计算过程如下;

     10011
x    10110
-------------0000010011100110000010011
------------110100010

得到的计算结果 110100010 就是 418.

其实这里的二进制计算就对应着这样一个过程, 要求 a * b 的结果, 就从右往左开始遍历 b 的每一位二进制值, 如果 b 的第 i 位是 1, 则把 a 左移 i 位的累值加到结果中; 如果 b 的第 i 位是 0, 不需要处理, 最后累加完的结果就是 a * b 的答案.

只使用位运算实现乘法的代码如下:

// 不使用算数运算实现两数相乘
public static int mulit (int a, int b) {// 就小学手算十进制类似// 让 a 的每一位去乘 b 的每一位int res = 0;while (b != 0) {if ((b & 1) != 0) {res = add(res, a);}a <<= 1;// 要注意 b 必须是无符号右移b >>>= 1;}return res;
}

4 . 实现除法

在实现除法的时候, 为了防止溢出, 我们可以先把两数转换成正数来进行计算; 最后在计算末尾再判断两个数的符号决定是否把由正数计算出来的结果取其相反数.

假设 a / b = c, 则 a = b * c, 使用位运算就需要结合二进制来思考, 这里假设:

a = b * (2^7) + b * (2^4) + b * (2^1), 则 c 的二进制一定是10010010.

抽象一下, 如果a = b * (2 ^ m1) + b * (2 ^ m2) + b * (2 ^ m3), 则 c 的 m1 位置, m2 位置, m3 位置一定是 1, 其他位置都是 0.

所以, 我们实现除法的思路可以转换成 a 是由几个 ( b * 2 的某次方) 的结果组成.

所以, 只使用位运算实现除法的核心代码如下:

// 判断是不是负数
public static boolean isNegavit(int num) {return num < 0;
}
// 这个适用于a和b都不是系统最小值的情况下
public static int div(int a, int b) {// 这里的除法一定要保证两个数都是正数, 如果有负数在计算逻辑最后加上即可int x = isNegavit(a) ? oppNum(a) : a;int y = isNegavit(b) ? oppNum(b) : b;int res = 0;// 计算的是 x / y// 每次循环 y 向左移动到和 x 最接近的值, 但要满足 y <= x, 如果是这个条件下, 也就是让 y 左移, 可能会溢出到符号位, 就可能会出错// 实际上将 x 向右移动到和 y 最接近的值, 移动的位数和上面也是一样的, 不过要满足 x >= y;for (int i = 30; i >= 0; i = minus(i, 1)) {if ((x >> i) >= y) {// 这个比特位一定是1res |= (1 << i);// x 减去 y << i;x = minus(x, y << i);}}// isNegavit(a) != isNegavit(b) 这个也可以用 isNegavit(a) ^ isNegavit(b) 来实现效果return isNegavit(a) != isNegavit(b) ? oppNum(res) : res;
}

其中

if ((x >> i) >= y) {// 这个比特位一定是1res |= (1 << i);// x 减去 y << i;x = minus(x, y << i);
}

就是让 a 不断尝试其值是否由 (b * 2的某个次方) 相加得到.

但只有上面的实现还不够, 这里是有一些特殊情况需要考虑的, 比如在 Java 中, int 类型的系统最小值Integer.MIN_VALUE取相反数的结果依然是Integer.MIN_VALUE.

所以, 除法的两个操作数如果有系统最小值的话需要单独的进行计算处理.

  1. 如果两操作数都是系统最小值, 结果就是 1;
  2. 如果只有除数 (b) 为系统最小值, 结果就是 0;
  3. 如果被除数 (a) 为系统最小值, 除数为 -1 时, 根据 LeetCode 题目要求, 结果就是Integer.MIN_VALUE / (-1) == Integer.MAX_VALUE;
  4. 如果被除数 (a) 为系统最小值, 除数为 -1Integer.MIN_VALUE时 (即a = Integer.MIN_VALUEb != -1 && b != Integer.MIN_VALUE), a / b应该通过如下方式来计算;
  • 可以先让先让系统最小值 +1 (a + 1), 此时就可以按照正常上面的正常流程去计算除法了, 即(a + 1) / b = c.
  • 接着计算a - (b * c) = d, 得到由于 a + 1 少/多算的.
  • 然后d / b = e
  • 最后将 ce 相加就得到了 a / b 的值, c + e = ((a + 1)/b) + ((a - (b * c)) / b) = a / b

除了这些特殊, 就是正常的流程了, 所以, 加上针对系统最小值的特殊判断的代码如下:

// 除法的计算如果有系统最小值需要进行特殊判断(因为Integer.MAX_VALUE取反再+1得到的还是原来值), 也就是没办法计算相反数
public static int divide(int a, int b) {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) {// 被除数为系统最小值// 且除数为-1if (b == oppNum(1)) {// 答案应该是 Integer.MAX_VALUE + 1 的这个值的, 但力扣系统返回 Integer.MAX_VALUE 就行了return Integer.MAX_VALUE;} else {// 系统最小值没法取相反数计算// 1. c = (Integer.MAX_VALUE + 1) / b , 先让系统最小值 +1 后再除以 b// 2. (Integer.MAX_VALUE - c * b) / b// 3. 再将 1 和 2 的结果相加节课int c = div(add(a, 1), b);return add(c, div(minus(a, mulit(c, b)), b));}} else {// 两数都不是系统最小值return div(a, b);}
}

完整实现的代码如下:

class Solution {// 不使用算数运算实现两数相加public static int add (int a, int b) {// 两个数的和等于两个数不进位相加和进位相加的和// a ^ b 得到的就是两数不进位相加的和// (a & b) << 1 得到的就是两数只相加进位的值// 一直循环至进位值为0计算结束int sum = a;while (b != 0) {sum = a ^ b;b = (a & b) << 1;a = sum;}return sum;}// 计算一个数字的相反数public static int oppNum (int num) {return add(~num, 1);}// 不使用算数运算实现两数相减public static int minus(int a, int b) {// a - b 就相当于 a + (-b)// 一个数的相反数等于这个数取反再加1return add(a, oppNum(b));}// 不使用算数运算实现两数相乘public static int mulit (int a, int b) {// 就和小学手算十进制类似// 让 a 的每一位去乘 b 的每一位int res = 0;while (b != 0) {if ((b & 1) != 0) {res = add(res, a);}a <<= 1;// 要注意 b 必须是无符号右移b >>>= 1;}return res;}// 不使用算数运算实现除法// 判断是不是负数public static boolean isNegavit(int num) {return num < 0;}// 这个适用于a和b都不是系统最小值的情况下public static int div(int a, int b) {// 这里的除法一定要保证两个数都是正数, 如果有负数在计算逻辑最后加上即可int x = isNegavit(a) ? oppNum(a) : a;int y = isNegavit(b) ? oppNum(b) : b;int res = 0;// 计算的是 x / y// 每次循环 y 向左移动到和 x 最接近的值, 但要满足 y <= x, 如果是这个条件下, 也就是让 y 左移, 可能会溢出到符号位, 就可能会出错// 实际上将 x 向右移动到和 y 最接近的值, 移动的位数和上面也是一样的, 不过要满足 x >= y;for (int i = 30; i >= 0; i = minus(i, 1)) {if ((x >> i) >= y) {// 这个比特位一定是1res |= (1 << i);// x 减去 y << i;x = minus(x, y << i);}}// isNegavit(a) != isNegavit(b) 这个也可以用 isNegavit(a) ^ isNegavit(b) 来实现效果return isNegavit(a) != isNegavit(b) ? oppNum(res) : res;}// 除法的计算如果有系统最小值需要进行特殊判断(因为Integer.MAX_VALUE取反再+1得到的还是原来值), 也就是没办法计算相反数public static int divide(int a, int b) {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) {// 被除数为系统最小值// 且除数为-1if (b == oppNum(1)) {// 答案应该是 Integer.MAX_VALUE + 1 的这个值的, 但力扣系统返回 Integer.MAX_VALUE 就行了return Integer.MAX_VALUE;} else {// 系统最小值没法取相反数计算// 1. c = (Integer.MAX_VALUE + 1) / b , 先让系统最小值 +1 后再除以 b// 2. (Integer.MAX_VALUE - c * b) / b// 3. 再将 1 和 2 的结果相加节课int c = div(add(a, 1), b);return add(c, div(minus(a, mulit(c, b)), b));}} else {// 两数都不是系统最小值return div(a, b);}}
}

当然, 按照原本的题意, 只是不能使用乘法, 除法和取余运算, 其他可以正常使用, 代码就简单了不少, 思路是一样的, 代码实现如下:

class Solution29 {public static boolean isNegavit(int num) {return num < 0;}public static int oppNum (int num) {return (~num) + 1;}public static int mulit (int a, int b) {// 就和小学手算十进制类似// 让 a 的每一位去乘 b 的每一位int res = 0;while (b != 0) {if ((b & 1) != 0) {res += a;}a <<= 1;// 要注意 b 必须是无符号右移b >>>= 1;}return res;}public static int div(int a, int b) {// 这里的除法一定要保证两个数都是正数, 如果有负数在计算逻辑最后加上即可int x = isNegavit(a) ? oppNum(a) : a;int y = isNegavit(b) ? oppNum(b) : b;int res = 0;// 计算的是 x / y// 每次循环 y 向左移动到和 x 最接近的值, 但要满足 y <= x, 如果是这个条件下, 也就是让 y 左移, 可能会溢出到符号位, 就可能会出错// 实际上将 x 向右移动到和 y 最接近的值, 移动的位数和上面也是一样的, 不过要满足 x >= y;for (int i = 30; i >= 0; i--) {if ((x >> i) >= y) {// 这个比特位一定是1res |= (1 << i);// x 减去 y << i;x -= (y << i);}}// isNegavit(a) != isNegavit(b) 这个也可以用 isNegavit(a) ^ isNegavit(b) 来实现效果return isNegavit(a) != isNegavit(b) ? oppNum(res) : res;}// 除法的计算如果有系统最小值需要进行特殊判断(因为Integer.MAX_VALUE取反再+1得到的还是原来值), 也就是没办法计算相反数public static int divide(int a, int b) {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) {// 被除数为系统最小值// 且除数为-1if (b == -1) {// 答案应该是 Integer.MAX_VALUE + 1 的这个值的, 但力扣系统返回 Integer.MAX_VALUE 就行了return Integer.MAX_VALUE;} else {// 系统最小值没法取相反数计算// 1. c = (Integer.MAX_VALUE + 1) / b , 先让系统最小值 +1 后再除以 b// 2. (Integer.MAX_VALUE - c * b) / b// 3. 再将 1 和 2 的结果相加节课int c = div(a + 1, b);return c + ((a - mulit(c, b)) / b);}} else {// 两数都不是系统最小值return div(a, b);}}
}

上述代码都是可以通过OJ的

img


http://www.ppmy.cn/news/59385.html

相关文章

HTTP代理出现503错误是什么意思,要如何修复?

在使用HTTP代理的时候&#xff0c;我们常常会遇到各种问题&#xff0c;想要解决&#xff0c;就需要根据返回码来解决。今天我们来说说&#xff0c;遇到HTTP 代理出现 503 服务不可用错误要怎么办&#xff0c;该如何解决呢&#xff1f; 首先&#xff0c;我们要明白&#xff0c;…

成为数据分析师,需要具备哪些技能?

随着互联网的发展&#xff0c;数据分析师的特点越来越明显&#xff0c;对数据分析师综合素质的要求也较高。 1、较强的数据挖掘、信息整理、和逻辑分析能力 数据分析&#xff0c;也是数据分析师的一个方向。 制作日常性的经营报表&#xff0c;对公司或者行业KPI指标进行拆解…

Java 中的线程是什么,如何创建和管理线程-上(十一)

Java 中的线程是指程序中可以独立执行的最小单位。在 Java 中&#xff0c;创建线程通常有两种方式&#xff1a;继承 Thread 类和实现 Runnable 接口。线程的管理包括控制线程状态、线程优先级、线程同步等。 一、Java 中的线程 线程是程序中能够独立执行的最小单位&#xff0…

JavaWeb05(删除增加修改功能实现连接数据库)

目录 一.实现删除功能 1.1 url如何传参&#xff1f; xx.do?参数参数值&参数名参数值 1.2 servlet如何拿对应值&#xff1f; //根据参数名拿到对应的参数值 String str req.getParameter("参数名") 1.3 如何询问&#xff1f; οnclick"return con…

20230501-win10-制作U盘启动盘-firpe

20230501-win10-制作U盘启动盘-firpe 一、软件环境 zh-cn_windows_10_consumer_editions_version_22h2_updated_march_2023_x64_dvd_1e27e10b.isofirpe 1.8.2标签&#xff1a;firpe win10分栏&#xff1a;WINDOWS 二、硬件环境 8G或以上的U盘一个FX86笔记本一台 三、官方下…

自动驾驶中地图匹配定位技术总结

引言 汽车定位是让自动驾驶汽车知道自身确切位置的技术&#xff0c;在自动驾驶系统中担负着相当重要的职责。汽车定位涉及多种传感器类型和相关技术&#xff0c;主要可分为卫星定位、惯性导航定位、地图匹配定位以及多传感器融合定位几大类。其中地图匹配定位技术利用道路物理…

深度学习入门篇1

1. 目前流行的深度学习框架简介 深度学习框架&#xff08;点击跳转&#xff09; 2.神经网络工具箱torch.autograd与torch.nn torch.autograd库虽然实现了自动求导与梯度反向传播&#xff0c;但如果我们要完成一个模型的训练&#xff0c;仍需要手写参数的自动更新、训练过程的…

第六章结构型模式—代理模式

文章目录 代理模式解决的问题概念结构 静态代理动态代理织入的概念JDK 动态代理JDK 动态代理分析 CGLIB 动态代理 三种代理的对比JDK 和 CGLIB 的区别动态代理和静态代理的对比代理模式的优缺点使用场景 结构型模式描述如何将类或对象按某种布局组成更大的结构&#xff0c;有以…