7月初的时候挑战了一下LeetCode的第29题(中等难度,彷佛没什么值得夸耀的),题目要求在不使用除、乘,以及模运算的状况下,实现整数相除的函数。node
既然被除数和除数都是整数,那么用减法就能够实现除除法了(多么naive的想法)。一个trivial的、用JavaScript编写的函数能够是下面这样的(为了简单起见,只考虑两个参数皆为正整数的状况)git
function divide(n, m) { let acc = 0; while (n >= m) { n -= m; acc += 1; } return acc; }
如此朴素的divide
函数提交给LeetCode是不会被接受的的——它会在像2147483648除以2这样的测试用例上超时。能够在本地运行一下感觉下究竟有多慢github
➜ nodejs time node divide.js 2147483648/2=1073741824 node divide.js 1.14s user 0.01s system 99% cpu 1.161 total
那么有没有更快的计算两个整数的商的算法呢?答案固然是确定的。算法
一眼就能够看出,运行次数最多的是其中的while
循环。以2147483648除以2为例,while
循环中的语句要被执行1073741824次。为了提高运行速度,必须减小循环的次数。shell
既然每次从n
中减去m
须要执行n/m
次,那么若是改成每次从中减去2m
,不就只须要执行(n/m)/2
次了么?循环的次数一会儿就减小了一半,想一想都以为兴奋啊。每次减2m
,而且自增2的算法的代码及其运行效果以下ide
➜ nodejs cat divide2.js function divide(n, m) { let acc = 0; let m2 = m << 1; // 由于题目要求不能用乘法,因此用左移来代替乘以2。 while (n >= m2) { n -= m2; acc += 2; } while (n >= m) { n -= m; acc += 1; } return acc; } console.log(`2147483648/2=${divide(2147483648, 2)}`); ➜ nodejs time node divide2.js 2147483648/2=1073741824 node divide2.js 2.65s user 0.01s system 99% cpu 2.674 total
尽管耗时不降反升,令场面一度十分尴尬,但根据理论分析可知,第一个循环的运行次数仅为原来的一半,而第二个循环的运行次数最多为1次,能够知道这个优化的方向是没问题的。函数
若是计算m2
的时候左移的次数为2,那么acc
的自增步长须要相应地调整为4,第一个循环的次数将大幅降低至268435456,第二个循环的次数不会超过4;若是左移次数为3,那么acc
的步长增至8,第一个循环的次数降至134217728,第二个循环的次数不会超过8。测试
显然,左移不能无限地进行下去,由于m2
的值迟早会超过n
。很容易算出左移次数的一个上限为优化
对数符号意味着即使对于很大的n
和很小的m
,上述公式的结果也不会很大,所以能够显著地提高整数除法的计算效率。spa
在开始写代码前,让我先来简单地证实一下这个方法算出来的商与直接计算n/m
是相等的。
记被减数为n
,减数为m
。显然,存在一个正整数N
,使得
令
,再令
,那么n
除以m
等价于
证实完毕。
从上面的公式还能够知道,新算法将本来规模为n
的问题转换为了一个规模为r
的相同问题,这意味着能够用递归的方式来优雅地编写最终的代码。
最终的divide
函数的代码以下
function divide(n, m) { if (n < m) { return 0; } let n2 = n; let N = 0; // 用右移代替左移,避免溢出。 while ((n2 >> 1) > m) { N += 1; n2 = n2 >> 1; } // `power`表示公式中2的N次幂 // `product`表明`power`与被除数`m`的乘积 let power = 1; let product = m; for (let i = 0; i < N; i++) { power = power << 1; product = product << 1; } return power + divide(n - product, m); }
这个可比最开始的divide
要快得多了,有图有真相
➜ nodejs time node divide3.js 2147483648/2=1073741824 node divide3.js 0.03s user 0.01s system 95% cpu 0.044 total
若是以T(n, m)
表示被除数为n
,除数为m
时的算法时间复杂度,那么它的递推公式能够写成下列的形式
但这玩意儿看起来并不能用主定理直接求出解析式,因此很遗憾,我也不知道这个算法的时间复杂度究竟如何——尽管我猜想就是N
的计算公式。
若是有哪位好心的读者朋友知道的话,还望不吝赐教。