不用*和/计算整数除法。请找出最快的方式。
解答:
虽然初始化一个计数变量,每当被除数减去除数的一次就自增一直到被除数小于除数这个暴力解法可行,但显然很慢。这是wiki answer答案,但它在很多情况下都不快,比如100/1。其执行的次数正好和相除的结果相同,用m表示除数,n表示被除数,时间复杂度是O(m/n)。
// Note: This only works for positive values!
int divide(int numerator, int denominator) {
int quotient = 0;
while(numerator >= denominator) {
numerator -= denominator;
quotient++;
}
return quotient;
}
下面看看另一种解法。
一般限制使用*和/时,很容易考虑使用位移运算来替代,因为对于无符号数,左移一位(在不溢出时)相当于乘以2,右移一位相当于除以2。如果在纸上进行除法的笔算,是只用到了乘法和减法的。但是一般的十进制整数除法和位运算有什么关系呢?为了将两者建立联系,必须把十进制数转化成二进制数,观察除法的进行情况来找规律。比如100/7,写成二进制来进行笔算,计算过程如下图:
这样就简单了,从这个式子可以看出,二进制除法笔算只涉及了减法和隐含的移位与大小比较,原先的乘法已经被移位所代替。因此,具体的编码,就是把用笔算除法的过程转化成代码而已。
不过,一般考虑使用除法的环境,必然要考虑除数是否为0。除数为0时这个除法是非法的,不能继续进行,需要报错。
带符号数右移的结果在C语言里是实现相关的,具体结果取决于实现,而不一定是用符号位补、用1补或者用0补最高位。为了避免这个陷阱,建议先确定结果——也就是商的符号,然后把被除数和除数都转化为无符号数,这样位移时就不会出错。
但是,这又涉及了有带符号数与无符号数的转换,它们二者的表示范围的问题是不同的。好在被除数和除数从带符号数转化为无符号数时并不会丢失数据,而且商的绝对值必然小于被除数的绝对值(因为除数是整数,为0时报错,大于等于1时才继续进行),这时把商转化回带符号数时也不会丢失数据,可以放心的进行。不过这一点最好在面试时告诉面试官你已经注意到了这个问题,肯定会为你的印象加分。
int division(int m,int n) {
//calculate m/n without * and /
unsigned int rest,divisor, op,result = 0;
int flag;
int bits = 0;
//bits用于记录商的1在哪一位
assert(n!=0);
if((m<0 && n>0) || ( m>0 && n<0 ))
flag = -1;
else
flag = 1;
rest = m>0?m:-m;
divisor = n>0?n:-n;
if(rest < divisor)
return 0;
op = divisor;
/* 2013.8.30 */
/*经过博客园园友infinityu的提醒重写 */
while(op<=rest) {
bits++;
op=op<<1;
}
op=op>>1;
bits--;
while(op>=divisor) {
if(rest>=op) {
rest-=op;
result += 1<<bits;
}
op = op>>1;
bits--;
}
/* 重写部分结束 */
return flag * result;
}
由于需要把被除数转化为二进制进行计算,最多做了其二进制表示位数次的减法,因此对于被除数m,算法复杂度为O(logm)。
稍作修改,把最后的小于除数divisor的result取出就是余数,这样就能把除法运算改写为取模运算%了。如果把参数表修改为传递结果地址,同时获得商和余数也是可以的。