LeetCode 29 Divide Two Integers
题目大意:给你俩32位的数,不能用除法、乘法和模运算,让求出它们的商
分析:
题目意思挺容易理解的,难一点的地方就是不能用这些运算,所以就需要去考虑除法的本质。
我们知道,四则运算中乘法运算是加法的演化,或者说本质上可以通过加法来理解,那么对于除法,也应该就对应和减法有一定的相关性。这也是我首先想到的思路:
对被除数不断的减去除数,同时对减去的次数计数,每减去一次自然加1,最后当被除数不够减的时候,计数的值就是商,比如
样例中的10除以3,减去3次之后,10变成1,小于3不够减,就结束。答案也就是3。用递归或者非递归(循环)都能很快的写出来,但是比较慢,因为如果给一个特殊的case就不太好使,比如这个题几个最蛋疼的点,被除数是INT_MAX,除数是1,这相当于从1加到INT_MAX。
所以在这个思想基础上,可以进行优化。我们完全没有必要按照每次只加一个除数的速度来考虑,借鉴二分的思想,我们可以每次把这个需要减去的数扩大一倍,而扩大一倍恰好又可以利用位运算来实现。还是刚才那个例子10除以3,可以这样来考虑,10比3大,3就扩大一倍变成6,然后10还是大于6,所以继续扩大一倍变成12,但是12超出了10,所以就结束了。这样一来就能加快被除数缩减的速度。
那现在的问题就是如何得到商,还是刚才的例子,10比3大,意味着至少是有一倍,所以不妨记一个值n为1,然后3扩大一倍变成6之后,10还是比6大,所以10至少又是6的一倍,自然就至少是3的2倍。可能这个例子还不够明显,如果换成17除以3,那现在6继续扩大一倍,变成12,17还是比12大,所以17至少是12的一倍,也就至少是6的两倍,3的四倍,这就很明显能看出来这个值n也是在同步增大两倍,弄清楚了这一点,接下来的过程就很简单了。回到刚才的例子,当6再扩大两倍就变成了12,10比12小,所以6变成12这一步就不能左移了,而是10减去6,得到4,这就意味着对于6这一部分算完了,能得到2的部分商,剩下的4继续作为运算(其实就是10 = 2 * 3 + 4),接下来的过程类似刚才的过程,4比3大,但是比6小,所以不需要左移,得到新的部分商1,然后接着4减去3,得到1,1此时小于3,连一倍都不够,也就是所谓的余数,所以可以跳出循环,到这里算法就结束了。
代码实现
class Solution {
using ll = long long;
public:
int divide(int dividend, int divisor) {
int flag = ( (ll)dividend < 0) ^ ((ll)divisor < 0) == 0 ? 1 : -1;
ll ans = 0;
ll temp, dvd = abs((ll)dividend), dvs = abs((ll)divisor);
int cnt;
while (dvd >= dvs){
temp = dvs;
cnt = 1;
while (temp << 1 <= dvd){
temp <<= 1;
cnt <<= 1;
}
dvd -= temp;
ans += cnt;
}
if (flag == -1) return flag * ans;
return abs(flag * ans) > INT_MAX ? INT_MAX : flag * ans;
}
};
代码细节
- 因为带有符号,所以先把它们的符号做一个判断,用flag保存,同号为1,异号为-1
- 需要考虑溢出的问题,也是这个题最操蛋的地方,有几个例子用到了-2147483648除以-1,或者是-2147483648除以1 ,算出来的值应该是2147483648和-2147483648。但2147483648这个值超出了32位可以表示的范围,我们在算法中为了防止溢出,是使用long long的大类型来保存ans,所以ans的值是可以保存2147483648,但这不是32位能表示的数,而应该是2147483647(INT_MAX),但是对于-2147483648,是32位可以表示的,所以应该返回-2147483648,这是需要考虑的一个细节,所以我的代码中分了两种情况返回。