这篇文章是我在LeetCode刷题时写的一篇题解,

因为我的解题思路非常独特,网上完全没看到过类似的实现,所以专门发上CSDN

其中有种解法,可以不用任何算术运算符,位运算符或Math对象实现整数加法

题目要求

写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。

示例:

输入: a = 1, b = 1
输出: 2

代码模板:

/*** @param {number} a* @param {number} b* @return {number}*/
var add = function(a, b) {}

输入输出限制:

a, b 均可能是负数或 0
结果不会溢出 32 位整数

常规解法

1.偏要做加法

既然要实现整数相加,为什么不用原生加法呢?

虽然题目要求我们不能在函数体内使用加法,但是我们可以反其道而行之,直接返回两数相加的结果:

var add = function(a, b) {return a + b
}

执行用时 / 内存消耗 超过 50% / 100% 的用户

照理说使用原生加法应该是效率最高的解法,但是可能判题系统有分析函数源码并特地降低成绩,因此成绩不好

2.普通的代码混淆法

既然题目中不能出现 “+”、“-”、“*”、“/” 四则运算符号,

那么我们可以用编码的方式,将四则运算符号编码成判题系统不能识别出的字符串,

然后再利用JavaScript动态语言的特性,将解码后的符号放入代码内,然后执行代码:

var add = function(a, b) {//return eval([a,b].join(decodeURIComponent('%2b')))return Function('a','b',`return a${String.fromCharCode(43)}b`)(a,b)}

执行用时 / 内存消耗 超过 100% / 100% 的用户

可以明显的看到,尽管我们兜了个圈子,但执行用时和内存消耗都大幅减小,说明判题系统很有可能做了代码识别的操作

上下两种方法可以任选其一。虽然通常来说出于性能和安全性考虑要少用eval执行代码,但是它在本题中效率不错

3.不错的递归位运算解法

先讲讲加法的二进制原理

学过计算机组成原理的都知道,计算机硬件里的半加器是通过异或逻辑门(XOR gate)和与逻辑门(AND gate)实现的,

放几张计算机科学速成课里的截图:

从异或门的真值表可以看出,两个二进制位经过异或得出的结果位,看起来很像是执行了二进制加法,

即0+0=0,0+1=1,1+0=0,1+1=10

当XOR门的两个输入都是1时,输出为0,但我们想实现的二进制加法的结果应该是10,少了前面的一个进位1,

因此我们需要在我们的XOR门旁边加上一个AND门,只有当两个输入都是1时,输出1,表示进位:

这样我们就能造出了一个半加器,能够实现两个二进制位的加法。

但是我们的这个半加器现在还不够用,因为我们的半加器只能处理当前位的加法,而不能接收之前位的加法结果的进位

(即只能处理一位加法,不能处理多位加法)

举个例子,我们有两个整数A和B,有个半加器负责处理第0位的加法A0+B0,还有个半加器负责处理A1+B1,

那么显然,处理A1+B1的半加器只有两个输入位,还缺一个输入位处理上一位的进位,因此我们需要引入全加器

全加器负责将这一位的sum和上一位的carry加起来,再将这一位算出的carry和前面算出的carry进行或(OR)运算,

就能得出新的carry:

(图中的A,B指两个二进制位的输入,C,S分别表示Carry In进位和Sum和)

然后将一个半加器(第0位没有进位输入)和七个全加器(后面的位都有可能需要进位)连起来,就是一个八位的二进制加法器

那么讲这么久,对我们的解题有什么帮助呢?

我们知道,任何数字在(现代)计算机中都是以二进制形式进行传输和处理的,

我们的加法函数,在二进制视角上,就是在对整数a和b的每个二进制位都调用一次二进制加法,

将对应二进制位相加的结果,再加上上一个位的进位,

然后再将这个位的进位传入下一个位的加法过程中,再调用二进制加法,再将进位传入下一个。。。直到没有进位

那么我们怎么用JavaScript代码实现呢?

首先我们看看JavaScript语言中XOR,AND,和左移位运算符的使用(直接抄MDN的描述)

^(按位异或):对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0。

&(按位与):对于每一个比特位,只有两个操作数相应的比特位都是1时,结果才为1,否则为0。

<<(按位左移):将 a 的二进制形式向左移 b (< 32) 比特位,右边用0填充

提示1:在JavaScript中,位运算符将其操作数当成32位有符号整数看待

提示2:按位,指的是对于32位整数的每一位都执行相同的操作

那么我们可以发现,对于我们题目要求输入的两个32位整数a,b而言,

a ^ b ,相当于对所有的位求对应的结果位,将所有位的值都变成对应位相加所得的 sum 值,

a & b,相当于对所有的位求对应的进位,将所有位的值变成对应位相加所得的 carry in 值,

我们在半加器中可以看到,进位和结果位并不在同一位置上,

进位因为需要加到下一位上,所以在二进制表示上要比结果位更前,也就是需要左移一位,

所以我们的进位(a & b)在二进制中需要表示成(a & b)<< 1,

那么我们可以很容易想到:a + b = 对应位所有的 sum + 对应位所有的 carry in,即:

a + b = ( a ^ b ) + ( ( a & b ) << 1 )

注意在JavaScript中左移<<的优先级要低于加法+,为了只让进位左移,我们需要在右边的进位外再套一个括号

上面的这个公式看起来很完美,但我们这题用不了,因为我们的题目要求是,不能在代码内使用“+”符号,

因此我们必须消灭等号右侧的加号,但这似乎又陷入了一个死胡同:

有什么办法能够不用加法,将所有的进位和所有的结果位相加呢?

答案是:将进位和结果位任意作为a,b,放进之前的 a ^ b 和 ( ( a & b ) << 1 ) 里再算多次,直到进位为0

我们先说明,我们没有假定a,b谁是进位,谁是结果位,

只要代码逻辑符合上述所说,那么不管进位和结果位有没有互换位置,最终结果都是一样的

为什么答案是这样的呢?思路其实非常巧妙,读一读下面的步骤你就懂了:

1.输入两个数 a,b

2.计算两个数相加得到的进位和结果位

3.需要将进位和结果位相加

4.输入两个数进位,结果位

5.计算进位和结果位相加得到的进位①和结果位①

6.需要将进位①和结果位①相加

......

n-1.输入两个数进位m和结果m

n.没有进位,直接返回结果位

只要你读明白了,代码就能轻松写出来了,

我们可以使用递归的方式实现,也可以使用交换值的方式实现,我们先给出递归形式的解法:

var add = function(a,b){return b == 0 ? a : add( a ^ b, (a & b) << 1 )
}

执行用时 / 内存消耗 超过 70% / 100% 的用户

JavaScript使用64位浮点数储存数字,由于位运算会将操作数强制转换为32位有符号整数,

所以不同于其他语言,在JavaScript中使用位运算会有部分性能损失

4.不错的循环位运算解法

有了上面的解释,相信你也不难想到循环形式的解法,这种解法可能看起来会更好懂一些:

var add = function(a,b){while(b!=0){[a,b] = [ a ^ b, (a & b) << 1 ]}return a
}

执行用时 / 内存消耗 超过 70% / 100% 的用户

这里主要使用了 ES6 里的解构赋值,来避免使用临时变量,结构上看起来更清晰一些

非常规解法

5.不用任何算术运算符,位运算符或Math对象实现整数加法

这真的可能吗?不用之前提到过的任何方法,连位运算符和Math对象也不用?当然可以。

这里的算术运算符指的是:(来自百度百科)

+(加号) 加法运算 (3+3)

–(减号) 减法运算 (3–1) 负 (–1)

*(星号) 乘法运算 (3*3)

/(正斜线) 除法运算 (3/3)

%(百分号) 求余运算10%3=1 (10/3=3·······1)

^(乘方) 乘幂运算 (3^2)  (这个符号在这里不是我们之前说的异或)

! (阶乘) 连续乘法 (3!=3*2*1=6)

|X| x为任何数 (绝对值) 求正 (|1|)

位运算符指:(还是来自百度百科)

& 按位与

| 按位或

^ 按位异或

~取反

<<左移

>>右移(包括逻辑右移和算术右移)

废话不多说,直接上代码。

var add = function(a,b){return (function(a,b){if(a==0 || b == 0){return a || b}function negative(num){//将正数变为负数return Number([ [].indexOf('wth').toString()[0],num ].join(''))}function abs(num){//取绝对值return num >= 0 ? num : Number( num.toString().slice(1) )}if( a>0 && b>0 ){ //正数相加return Array( abs(a) ).concat( Array(abs(b)) ).length}if( a<0 && b<0 ){ //负数相加return negative( Array(abs(a)).concat(Array(abs(b))).length )}if( a > b ){if( abs(a) > abs(b) ){ //大正数+小负数let t = Array(a)t.splice(b)return t.length}else if( abs(a) < abs(b) ){ //小正数+大负数,即大负数绝对值-小正数取反let t = Array(abs(b))let tmp = t.splice(a)return negative(tmp.length)}}else{//提示:在严格模式下无法在匿名函数内调用自身return arguments.callee(b,a)}})(a,b)}

你看懂了吗?

先说明一下,有的人可能会问,你不是说不用绝对值吗?为什么还有个abs函数?

绝对值的计算是非常必要的,如果没有绝对值,就无法比较两数距离原点0的距离,那么就无法处理两数在0两侧的情况,

JavaScript本来也没有原生计算绝对值的运算符,如果你觉得绝对值函数不对劲,你把它当成防抱死函数不就好了嘛

接下来我解释一下我的思路。

1.在JavaScript内如果不用任何算术运算符,位运算符或Math对象来实现整数加法,就只可能通过原生数据结构来实现

2.再读一遍题,整数加法,输入可以是正数,负数或者0,数据结构本身必须可变,而且能映射成数值

3.在JavaScript中算上 Symbol 只有七种基本数据类型,只有数组 Array 类型满足要求

4.数组可以合并,模拟正数相加,可以切片,模拟正数作差,但最关键的是:这两个数组操作无法产生负值

5.既要产生负数,代码里又不能出现负号(题目要求),就只能通过数组索引获得负数

6.由于代码内不能出现加号(题目要求),因此只能通过数组的join方法实现字符串拼接

7.a和b如果是一正一负,那么理论上需要处理4种情况,浪费时间而且没有必要

8.为了减少代码量,将一正一负的相反的情况(一负一正)的a,b换位再传入自身参数中,问题解决

思路是不是很巧妙呢?

这种方法缺陷显而易见,需要频繁分配大量内存空间,性能极差,

尽管如此,这玩意还是能在Leetcode上顺利提交通过的,是不是很神奇?

如果你喜欢我这篇文章,点个赞吧!原创不易,求多支持!

JavaScript如何实现加法?相关推荐

  1. javascript乘法和加法_JavaScript大数相加相乘的实现方法实例

    前言 JavaScript 中的最大安全整数是 2 ^{53} – 1 ,即 9007199254740991,当我们进行超出这个范围的数值计算的时候就无法得到精确的值,而是一个近似值,比如我们计算 ...

  2. JavaScript中的加法运算

    <head runat="server"> <title>JavaScript实现加法计算器</title> <script type=& ...

  3. javascript乘法和加法_Web前端:JavaScript中的NaN是什么?

    大家好,我来了,本期为大家带来的前端开发知识是"Web前端:JavaScript中的NaN是什么?",有兴趣做前端的朋友,一起来看看吧! JavaScript中的数字类型包含整数和 ...

  4. javascript乘法和加法_前端基础:JavaScript

    Introduction 脚本语言叫做动态语言,它是一种解释型语言,它一般由文本编辑器编辑.脚本语言,一般它不能单独运行,需要嵌入到其它语言中. JavaScript 是比较流行的一种脚本语言,通过 ...

  5. html中加法计算器的代码,JavaScript实现简易加法计算器

    本文实例为大家分享了JavaScript实现加法计算器的具体代码,供大家参考,具体内容如下 具体要求: 1.页面布局: 2.还需要在点击计算按钮之后在页面上显示计算结果 具体实现: 计算器 funct ...

  6. javascript乘法和加法_JS加减乘除运算

    //加法 Number.prototype.add = function(arg){ var r1,r2,m; try{r1=this.toString().split(".")[ ...

  7. javascript乘法和加法_js 大整数加法、乘法、除法

    有一定的编程经验的人都知道,当我们对数据操作的时候,若数据过大,计算机将这个大数从十进制的转为二进制的,是没有那个类型的放的了的,因此,我们经常将一个大数转化为字符串来操作.接下来我将演示大整数的加法 ...

  8. html中加法,javascript 实现加法运算详解

    例子,javascript 实现加法运算. 复制代码 代码示例: javascript 实现加法运算 function calsum(){ var a=parseint(document.getele ...

  9. JavaScript和HTML实现的简单计算机

    代码 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title& ...

最新文章

  1. Java连接数据库 JDBC
  2. python新手任务:python循环嵌套
  3. python脚本如何监听终止进程行为,如何通过脚本名获取pid
  4. 毕业三年,贷款40万创业之后我又做回了程序员
  5. DreamWeaver下如何应用CSS样式
  6. 视觉slam十四讲ch6曲线拟合 代码注释(笔记版)
  7. NoSql理解+传统关系型数据库ACID+Nosql的CAP+BASE的理解
  8. python降维可视化 自编码_deep learning 自编码算法详细理解与代码实现(超详细)...
  9. 智能一代云平台(三十六):项目中如何做到避免传递依赖
  10. 微信公众账号开发入门准备
  11. win7 64位系统PSD缩略图补丁
  12. 计算机桌面动态壁纸,动态桌面壁纸,详细教您电脑动态桌面壁纸怎么设置
  13. 第二章:WINDOWS的一些技巧
  14. python图片转换成文字_在python中将图像转换为字节文字 - python
  15. 命令启动oracle实例,【单选题】启动oracle数据库实例的命令是
  16. 计算机音乐名词解释,音乐常见名词解释
  17. matlab数学建模-非线性规划(无约束规划、有约束规划)
  18. 【Kotlin -- 知识点】Kotlin 中的委托
  19. WebRTC系列-RTCDataChannel发送非音视频数据
  20. 使用rufus制作Windows Server 2012 R2 U盘_wentfar·tsao

热门文章

  1. c 语言loadimage方法吗,CBitmap, HBITMAP和LoadImage
  2. python使用pip install安装django报错
  3. iOS中制作动态链接库Framework
  4. WiFi共享精灵的使用说明
  5. 编程小白的人工智能路之Gabor滤波提取掌纹特征并对比掌纹相似度(一)
  6. 微信小程序中,图片的位置设置
  7. HBuilder常用快捷键
  8. ebpf中的percpu map的注意事项与剖析
  9. 高性能 HTTP 负载测试工具 Vegeta
  10. 神舟z7m安装Linux,神舟战神Z7M重装win10系统教程