题目内容
Divide two integers without using multiplication, division and mod operator. 求出两个数相除的商,禁用乘法或者除法或者取模运算。
题目分析
最容易的想到的办法,是把除法转化为减法,就像把乘法转化为加法一样,提交后发现,这个做法超时了,比如遇到2147483647/3这种时候,被除数很大,除数很小,基本都会超时。所以应该要使用一个特别的方法来提高效率。我想应该是位运算了。但是不知该如何写。参考了一下官网答案。发现实在很巧妙。如下,我把它微微改了一下,更好理解一些:
//官网答案,我的微改版
class Solution {
public:
int divide(int dividend, int divisor) {
long long a=abs(dividend); //(long long) dividend中的(long long)是为了防止-2147483648带来的溢出,下行同理。
long long b=abs(divisor);
int res=0;
while(a>=b){
long long c=b;
for(int i=0;c<=a;i++){
a-=c;
res+=1<<i;
c=c<<1;
}
}
return ((dividend^divisor)>>31) ? (int)(-res) : (int)(res);//判断是否为负数
}
};
//官网实际答案
class Solution {
public:
int divide(int dividend, int divisor) {
long long a = abs((double)dividend);
long long b = abs((double)divisor);
long long ret = 0;
while (a >= b) {
long long c = b;
for (int i = 0; a >= c; ++i, c <<= 1) {
//std::cout<<"1-1 a="<<a<<", c="<<c<<" ,i="<<i<<" ,ret="<<ret<<std::endl;
a -= c;
ret += 1 << i;
//std::cout<<"1-2 a="<<a<<", c="<<c<<" ,i="<<i<<" ,ret="<<ret<<std::endl;
}
}
return ((dividend^divisor)>>31) ? (int)(-ret) : (int)(ret);
}
};
这个算法要理解起来,只需明白它提速的基本原理。
现在用两个例子谈谈它的基本原理。基本原理是,dividend非常大,所以把它切割成几块,一块一块地和divisor计算商,再把这些商都加起来就可以了。在切割的时候,各个小块是不均匀的。第1块的大小正好是divisor的大小,第2块的大小正好是divisor扩大2倍的大小,第3块的大小正好是divisor扩大2²倍的大小,第4块的大小正好是divisor扩大2³倍的大小......以此类推,直到把dividend分割完毕。以计算 49/7 = ? 为例子来看看这个过程。把上面代码中的注释恢复。可以看到下面的结果
1-1 a=49, c=7 ,i=0 ,ret=0
1-2 a=42, c=7 ,i=0 ,ret=1
1-1 a=42, c=14 ,i=1 ,ret=1
1-2 a=28, c=14 ,i=1 ,ret=3
1-1 a=28, c=28 ,i=2 ,ret=3
1-2 a=0, c=28 ,i=2 ,ret=7
49 / 7 = 7
c就是每次切割的小块,a就是每次拿走一小块之后a还剩下的数。
上面这个例子展示了正好整除的情况,那么其实还有不整除的情况,这样切出的小块中,最后一块是没法进入for循环中的,这时while循环就起作用了,来让最后单出来的一块也参与运算,直到a<b时停止,此时a被分割结束了,a/b=0不再对商的累加做贡献了。用64/7 = ?这个例子来看看。结果如下
1-1 a=64, c=7 ,i=0 ,ret=0
1-2 a=57, c=7 ,i=0 ,ret=1
1-1 a=57, c=14 ,i=1 ,ret=1
1-2 a=43, c=14 ,i=1 ,ret=3
1-1 a=43, c=28 ,i=2 ,ret=3
1-2 a=15, c=28 ,i=2 ,ret=7
1-1 a=15, c=7 ,i=0 ,ret=7
1-2 a=8, c=7 ,i=0 ,ret=8
1-1 a=8, c=7 ,i=0 ,ret=8
1-2 a=1, c=7 ,i=0 ,ret=9
64 / 7 = 9
另外,一开始我的代码是这样的,超时的代码
//我的超时版代码
int divide1(int dividend, int divisor) {
if (divisor==0||dividend==0)
return 0;
if(divisor==1)
return dividend;
else if(divisor==-1)
return -dividend;
int negative_flag=0;
if((dividend<0)&&(divisor>0)){
dividend=-dividend;
negative_flag=1;
} else if((dividend>0)&&(divisor<0)) {
divisor=-divisor;
negative_flag=1;
}else if((dividend<0)&&(divisor<0)){
divisor=-divisor;
dividend=-dividend;
}
int count=0;
while(dividend>=divisor){
count++;
dividend-=divisor;
}
count=(negative_flag==1)?(-count):(count);
return count;
}
小结:
(1) for循环的条件体写法学习了,初始条件用i表示,结束条件用a表示。原来还可以初始条件和结束条件用不同的变量来表示。
update : 2015-01-04
虽然大概知道上面那个写法的原理,但是真正自己写出来时,还是要经过一翻调试和egd case的检验。
//100ms
class Solution {
public:
int divide(int dividend, int divisor) { //eg. 100 / 3 = 33;
if (dividend == 0 || divisor == 0) return 0;
if (dividend == INT_MIN && divisor == -1) return INT_MAX;
if (divisor == 1) return dividend;
long long l_dividend = abs((long long)dividend);
long long l_divisor = abs((long long)divisor);
// 用long long, 否则int a = abs(INT_MIN) = INT_MIN;
long long a = l_dividend;
long long b = l_divisor;
long long res = 0;
long long each = 1;
while (a >= b) { //如果a < b,可以return0.
//std::cout<<"a = "<<a<<", b = "<<b<<", res = "<<res<<", each = "<<each<<std::endl;
while (a >= b) {
//std::cout<<"b="<<b<<", res = "<<res<<"each = "<<each<<std::endl;
each = each<<1;
b = b <<1;
}
res += (each>>1);
a -= (b>>1);
each = 1;
b = l_divisor;
}
return ((dividend ^ divisor)>>31) ? -res:res; //异或
}
};