ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

383,不使用“+”,“-”,“×”,“÷”实现四则运算

2021-06-14 23:54:53  阅读:241  来源: 互联网

标签:return 实现 四则运算 System int 383 static 我们 进位


watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

Youth, even in its sorrows, always has a brilliancy of its own. 

青春,即使在它的悲哀时也是辉煌的。

 

基础知识

从我们开始上学的时候就知道,如果要实现加法运算就要使用“+”符号,如果要实现减法运算就要使用“-”符号……,甚至在今天的计算机中也是一样的,我们只知道怎么使用,但很少去关注他的底层是怎么实现的。如果突然哪天给你一道面试题,让你不使用"+"来实现两个数相加,你该怎么做呢,今天我们就来看一下该怎么实现。

一:不使用“+”实现两个数相加

我们先来看一道非常简单的题,在计算机中数字是由二进制位表示的,也就是说是由0和1组成的,如果我们要实现0和1之间的加法该怎么实现呢,他会有4种组合方式

1,0+0=00

2,0+1=01

3,1+0=01

4,1+1=10

我们发现一个很重要的规律,就是只有1+1有进位,其他的都没进位。所以我们判断有没有进位只需要判断a&b是否等于1即可,而a+b的值(不考虑进位)只需要计算a|b即可,看明白了这点,代码就呼之欲出了

1private static int add1(int a, int b) {
2    int c = (a & b) << 1;//进位的值
3    int d = a ^ b;//不考虑进位,相加的值
4    return c | d;//或者 return c ^ d;
5}

a和b要么是1要么是0,所以这里最多也只有一个进位,很好理解。但我们计算二进制的加减法不光只有1个0或1,可能会有好多个1或0,那我们该怎么实现呢。比如a=13(1101),b=9(1001),我们该怎么计算a+b的结果。首先如果我们不考虑进位问题,那么a+b的运算会是下面这样

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

但实际上最前面和最后面的1都有了进位。

 

1,我们看到如果不考虑进位,那么a+b的结果其实就是a^b的结果,我们该怎么把进位问题也考虑在内呢,实际上只有1+1的时候才会出现进位,1+0或者0+0都不会出现进位,所以我们首先想到的是&运算

 

2,这里我们计算一下a&b的结果是1001,我们知道当&运算的结果为1的时候,说明参与&运算的两个都是1,既然两个都是1,那么相加的时候就肯定会有进位,所以他们进位的值实际上是10010((a&b)<<1),然后在和0100相加就是我们要求的结果,10010+00100=10110,10110实际上就是22,也就是13+9的结果

 

3,但我们好像忽略了一个问题,就是这道题要求不能使用加减乘除符号,而上面我们分析的时候使用了加号,所以明显不行。通过上面的分析实际上我们已经发现了一个规律,就是a+b通过^和&运算之后又再执行相加操作,所以我们首先想到的是递归,我们来看下代码

1private static int add2(int a, int b) {
2    if (a == 0 || b == 0)
3        return a ^ b;
4    return add2(a ^ b, (a & b) << 1);
5}

第3行表示的是如果a==0就返回b,如果b==0就返回a,这种写法少了一个if语句的判断会更简洁。为了验证代码的准确性我们随便找几个数据测试一下

1    int[] array = {1, 1, 1, 0, 0, 1, 0, 0, 13, 9, 1, -1, -Integer.MAX_VALUE, Integer.MAX_VALUE, -8, -9};
2    for (int i = 0; i < array.length / 2; i++) {
3        System.out.println(array[i << 1] + "+" + array[(i << 1) + 1] + "=" + add2(array[i << 1], array[(i << 1) + 1]));
4    }

上面的代码可以不用看,我们来看一下运行结果

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

经过测试,发现我们的代码完全正确,没有使用“+”实现了两个数相加。上面的递归我们还可以改为非递归的方式

1private static int add3(int a, int b) {
2    while (b != 0) {
3        int temp = a ^ b;
4        b = (a & b) << 1;
5        a = temp;
6    }
7    return a;
8}

这个也很好理解,每次计算的时候要对a和b进行重新赋值,然后再不断的循环,直到b等于0的时候停止循环,我们知道这里在运算的时候b表示的是进位的值,当b等于0的时候就表示没有进位,没有进位就退出循环,这就是使用位运算来实现加法。我们假设a=13,b=9来画个图加深一下理解

 

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

 

二:不使用“-”实现两个数相减

既然加法都实现了,那么减法就更容易了,a-b,直接改为a+(-b)即可,那么请等一下,我们不是说不能使用“-”吗,这里明显有了“-”符号,肯定不符合规则,那么别着急,在计算机中一个数的相反数还可以用另一种方式来表示,那就是

a的相反数是~a+1

上面“+”我们已经实现了,“~”不属于四则运算符,所以代码也很容易写出

1private static int subtraction(int a, int b) {
2    return add3(a, add3(~b, 1));
3}

这种实现就更简洁了,直接一行代码搞定,代码中add3(~b,1)表示的是-b。如果不使用加法是否能实现两个数相减呢,其实也是可以的,我们这样来思考一下,比如a-b

 

1,如果b等于0,我们直接返回a即可。如果b不等于0,我们可以先把a和b上同为1的数字给去掉,那么怎么去掉呢,其实很简单,我们先要计算c=a&b,那么c中为1的位置在a和b中相对应的位置上也是1,然后再通过异或运算就可以把它给移除。

 

2,在经过第一步执行之后,a和b在相同的位置上要么都是0,要么一个0一个1,不可能全是1了,那么下面就要会分为3种情况了(我们先不考虑因不够减而借位的问题)

(1),如果a和b对应的位置上都是0,那么结果对应的位置上也是0。

(2),如果a对应的位置是1,b对应的位置是0,结果对应的位置是1。

(3),如果a对应的位置是0,b对应的位置是1,结果对应的位置是1。(向前借一位1)

 

所以在不考虑借位的情况下,对应位置上的结果其实就是a|b(对应位置都为1的在第一步就已经被踢出了),那么实际计算的时候我们不可能不考虑借位的问题,所以实际结果是(a|b)-(b<<1),但这里又出现了“-”符号,所以不符合要求,这时我们可以使用递归的方式来解决,代码如下

1private static int subtraction2(int a, int b) {
2    if (b == 0)
3        return a;
4    int c = a & b;
5    //下面两行是把a和b中相同位置为1的都消去
6    a ^= c;
7    b ^= c;
8    return subtraction2(a | b, b << 1);
9}

当然我们还可以把它改为非递归的方式,像下面这样

 1private static int subtraction3(int a, int b) {
2    while (b != 0) {
3        int c = a & b;
4        a ^= c;
5        b ^= c;
6        a |= b;
7        b <<= 1;
8    }
9    return a;
10}

我们还是找几组数据来测试一下吧

1    int[] array = {1, 1, 1, 0, 0, 1, 0, 0, 13, 9, 1, -1, Integer.MAX_VALUE, Integer.MAX_VALUE, -8, -9, 100, Integer.MAX_VALUE};
2    for (int i = 0; i < array.length / 2; i++) {
3        System.out.println(array[i << 1] + "-" + array[(i << 1) + 1] + "=" + subtraction2(array[i << 1], array[(i << 1) + 1]));
4    }

上面的我们可以不用看,直接看运行结果就行了

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

我们以a=13,b=10来画个图加深一下理解

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

 

三:不使用“×”实现两个数相乘

我们先来看个例子,比如13*9,计算方式如下

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

由上面公式我们可以看出只有b的某一位是1的时候和a相乘才有意义,如果b的某一位是0,那么和a相乘则永远都是0,所以我们计算的时候逐步遍历b的每一位,只有当他为1的时候才进行运算,我们来看下代码

 1//求一个数的相反数
2private static int negative(int a) {
3    return add3(~a, 1);
4}
5
6private static int mult(int a, int b) {
7    int x = a < 0 ? negative(a) : a;//如果是负数,先转为正数再参与计算
8    int y = b < 0 ? negative(b) : b;
9    int res = 0;
10    while (y != 0) {
11        if ((y & 1) == 1)
12            res = add3(res, x);
13        x <<= 1;
14        y >>= 1;
15    }
16    return (a ^ b) >= 0 ? res : negative(res);
17}

我们还是来找一组数据测试一下,验证我们代码的正确性

1    int[] array = {1, 1, 1, 0, 0, 1, 0, 0, 13, 9, 1, -1, -8, -9, 100, 99};
2    for (int i = 0; i < array.length / 2; i++) {
3        System.out.println(array[i << 1] + "×" + array[(i << 1) + 1] + "=" + mult(array[i << 1], array[(i << 1) + 1]));
4    }

上面代码不用看,我们直接来看一下打印的数据,结果丝毫不差

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

 

四:不使用“÷”实现两个数相除

a÷b的含义是a中包含多少个b,比如6÷3=2,7÷3=2,这里我们实现的除法和计算机中两个int类型相除结果是一样的,只记录商的值,余数会被舍去,所以我们想到的一种解法是用a不断的减去b,并记录减了多少次,所以代码很容易想到,我们来看下

1private static int div1(int a, int b) {
2    int x = a < 0 ? negative(a) : a;
3    int y = b < 0 ? negative(b) : b;
4    if (x < y)
5        return 0;
6    return (a ^ b) >= 0 ? div1(subtraction(a, b), b) + 1 : div1(add3(a, b), b) - 1;
7}

上面的+1,-1直接改为上面的加法和减法即可,这里我为了方便阅读代码就没写。这种递归的实现效率不是很高,如果a非常大,b又比较小,很容易出现堆栈溢出异常,所以我们还可以把它改为非递归

 1private static int div2(int a, int b) {
2    int x = a < 0 ? negative(a) : a;
3    int y = b < 0 ? negative(b) : b;
4    int ocunt = 0;
5    while (x >= y) {
6        x = subtraction3(x, y);
7        ocunt++;
8    }
9    return (a ^ b) >= 0 ? ocunt : -ocunt;
10}

这种虽然不会出现堆栈溢出异常了,但如果b是1,a是一个非常非常大的数,这样一直减下去也是非常慢的,我们还可以换种思路,每次减去的不是b,而是b的倍数,我们来看下代码

 1private static int div3(int a, int b) {
2    if (a == 0 || b == 0)
3        return 0;//b不能为0,如果b是0我们应该抛异常的,这里简单处理就没抛
4    int x = a < 0 ? negative(a) : a;
5    int y = b < 0 ? negative(b) : b;
6    int result = 0;
7    for (int i = 31; i >= 0; i--) {
8        if ((x >> i) >= y) {
9            result = add3(result, 1 << i);
10            x = subtraction3(x, y << i);
11        }
12    }
13    return (a ^ b) >= 0 ? result : -result;
14}

我们找一组非常极端的数据来测一下上面两种方法,看一下效率到底相差多少倍

1    int a = Integer.MAX_VALUE;
2    int b = 1;
3    long time = System.nanoTime();
4    System.out.println(a + "÷" + b + "=" + div2(a, b));
5    System.out.println("优化之前的时间:" + (System.nanoTime() - time));
6    time = System.nanoTime();
7    System.out.println(a + "÷" + b + "=" + div3(a, b));
8    System.out.println("优化之后的时间:" + (System.nanoTime() - time));

我们来看一下结果

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

这个时间相差还是非常大的,一个30多亿纳秒,一个两万多纳秒,相差十几万倍。最后我们再找一组数据测试一下我们的代码是否正确

1    int[] array = {1, 1, 0, 1, 13, 9, 40, 3, 1, -1, -8, -9, 100, 99};
2    for (int i = 0; i < array.length / 2; i++) {
3        System.out.println("div2方法测试:" + array[i << 1] + "÷" + array[(i << 1) + 1] + "=" + div2(array[i << 1], array[(i << 1) + 1]));
4    }
5    System.out.println("----------------------------------------");
6    for (int i = 0; i < array.length / 2; i++) {
7        System.out.println("div3方法测试:" + array[i << 1] + "÷" + array[(i << 1) + 1] + "=" + div3(array[i << 1], array[(i << 1) + 1]));
8    }

我们来看下运行结果

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

结果和我们预想的完全一致。

 

 

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

380,缺失的第一个正数(中)

381,合并两个有序链表(易)

 

 

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

长按上图,识别图中二维码之后即可关注。

 

如果喜欢这篇文章就点个"在看"吧

标签:return,实现,四则运算,System,int,383,static,我们,进位
来源: https://blog.51cto.com/u_4774266/2902811

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有