「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」
前言
笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。
系列文章收录《算法》专栏中。
减法就是在加法的基础上把减法转换成加法来实现的,加法参考不用加减乘除做加法。
力扣上目前没有此题,是笔者自己的扩展。
问题描述
不用加减乘除做减法写一个函数,求两个整数之差,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
示例:
输入: a = 1, b = 1
输出: 0
提示:
a,b均可能是负数或 0- 结果不会溢出 32 位整数
确定学习目标
- 对《不用加减乘除做减法》的算法过程进行剖析。
- 位运算的用途
剖析
原始十进制分析
我们可以想下十进制的时候是怎么进行的,如42-51:
- 发现53大于42进行调换顺序,变成51-42,在大脑中缓存最后的结果需要取反
- 对调换顺序后的各个位进行相减,1-2发现小于0
- 向高位借一位,变成11-2等于9。十位从5变成4,所以十位相减变成0
- 调换顺序后的结果为9,最后取反变成-9
我们可以先总结下:
- 先对两个相减的数进行比较
- 当被减数小于减数的时候进行调换顺序,最后的结果记为需要取反
- 当被减数大于减数的时候不进行调换顺序,最后的结果记为不需要取反
- 基于第一步,对两数各个位进行相减,如果小于0就向高位借一位。
- 得出第二步的结果,判断第一步得出的结果需不需要取反
- 需要:对结果进行取反
- 不需要:对结果不进行取反
有没有发现,减法相对加法而言比较复杂,我们接下来看看计算机是怎么处理的。
计算机的处理
我们先想下计算机怎么复刻上面的步骤:
- 比较两个无符号的整数怎么处理?
- 如果小于0怎么向高位借一位?
- 怎么进行取反?
需要解决上问题,为啥不考虑直接在加法的基础上进行一定的转换?所以计算机直接把减法转换成加法:42-51 -> 42+(-51)。
那么-51怎么表示,也就是51的负数怎么表示,计算机的二进制的转换方式是取反加1(至于这个是怎么来的?计算机是用补码进行表示数字的,补码不管正负数都可以直接相加得出结果,所以这里只需要把正数的补码(还是自己)变成对应的负数的补码)。
下面直接上代码
代码
根据上面的结论写出以下代码:
/**
* 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)再进行相加
*
* @param a
* @param b
* @return
*/
public static int substract(int a, int b) {
b = add(~b, 1);
return add(a, b);
}