题目大意:给出两个int类型的除数与被除数,求出它们的商,但是不能使用乘法,除法,求模等方法。
注意点:
1. int类型整数的范围,除法过程会出现越界的情况
2. 对于正数负数的处理
2. 除数为0的情况
3. 不能使用乘除法
一. 第一想法
不使用乘除法来计算除法的商,那就意味着我们只能使用加减法以及位运算。将除法变成减法来处理,这是我的第一个想法,将被除数一直减去除数,直到被除数小于除数,返回减法的次数即可
对于当除数或者被除数出现负数的情况时,我们不能直接进行减法。必须将除数与被除数取绝对值后才能继续运算。
越界情况的处理,由于一个int类型的数的范围为 -2^31 ~ 2^31-1;所以当最小负数除以-1的情况下,会出现越界情况,必须单独处理
当然,除数为0的时候也要单独处理
根据上述思路,我写出了第一份错误的代码
class Solution {
public:
int divide(int dividend, int divisor) {
//处理越界
if(divisor == 0 || (divisor == -1 && dividend == INT_MIN)) return INT_MAX;
//处理负数
bool is_neg = 0;
if((dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0)){
is_neg = 1;
}
int result = 0;
//取绝对值
int did = abs(dividend);
int dir = abs(divisor);
//进行减法
while(did >= dir){
did -= dir;
result++;
}
return is_neg?-result:result;
}
};
二.分析错误并更换思路
首先,简单的运算已经可以正确通过,如7/3之类的。但是当出现-2147483647,1的情况时,输出答案却是0,这显然与我们的想法背道而驰。
简单的debug后,发现是c++库中的abs函数无法将-2147483647成功转化为一个正数,这就需要查阅一下这个问题出现在哪。于是,我需要自己实现一个abs函数来实现上述功能
unsigned int __abs(int value)
{
unsigned int copyed_value = value;
return (copyed_value > 0x80000000) ? -value : copyed_value;
}
分析:
因为32位下最小的int值为0x80000000,最高位符号位为1.当位对位拷贝到unsigned int中时,仍然是这个值.但其他的负数除了最高位为1外,其余位置也有值,比如-1的16进制表示为:0x80000001.所以,我们先执行位对位的拷贝,到copyed_value中.所以出现了判断情况:
(1)如果copyed_value是大于0x80000000的,说明value是负数,所以我们直接取相反数(-value);
(2)如果copyed_value是等于0x80000000的,说明value恰好是最小的那个负数,执行位对位拷贝后,copyed_value中存放的就是value的绝对值,所以返回copyed_value;
(3)如果copyed_value是小于0x80000000,说明value为正数.直接返回value或者copyed_value即可。
方法来源自 c++自己实现abs
经过修改后的代码:
class Solution {
public:
int divide(int dividend, int divisor) {
if(divisor == 0 || (divisor == -1 && dividend == INT_MIN)) return INT_MAX;
bool is_neg = 0;
if((dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0)){
is_neg = 1;
}
int result = 0;
unsigned int did = __abs(dividend);
unsigned int dir = __abs(divisor);
while(did >= dir){
did -= dir;
result++;
}
return is_neg?-result:result;
}
unsigned int __abs(int value)
{
unsigned int copyed_value = value;
return (copyed_value > 0x80000000) ? -value : copyed_value;
}
};
但是这样仍然无法通过测试,原因在于时间复杂度太高,出现了time limited错误。
这时候需要分析一下,我们能不能优化一下这个减法步骤,因为当被除数很大时,它所减法的步骤可能也很大,时间复杂度为O(n)。
我们用日常除法的思路思考一下,我们平常也不会一项一项的减,而是先找到除数较大的倍数的一个数与被除数先比较,然后再减去较小的倍数,直到无法继续减。这样的想法能大大减少除法的次数,时间复杂度为O(logn)
三. 修改代码
class Solution {
public:
int divide(int dividend, int divisor) {
//处理溢出情况
if(divisor == 0 || (divisor == -1 && dividend == INT_MIN)) return INT_MAX;
bool is_neg = 0;
//处理负数
if((dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0)){
is_neg = 1;
}
int result = 0;
unsigned int did = __abs(dividend);
unsigned int dir = __abs(divisor);
//减少减法的次数
while(did >= dir){
long temp = dir;
long mul = 1;
//找出除数倍数最大的数,但是小于被除数
while(did >= temp<<1){
temp = temp << 1;
mul = mul << 1;
}
did -= temp;
result += mul;
}
return is_neg?-result:result;
}
//绝对值函数
unsigned int __abs(int value)
{
unsigned int copyed_value = value;
return (copyed_value > 0x80000000) ? -value : copyed_value;
}
};
accepted!
四.总结
这题的思路还是比较清晰的,但是不用除法乘法来求商也是比较新颖,第一次做。如何分析关键步骤的复杂度十分关键,如何用算法思想来减少时间复杂度,先减去最大的,然后次之,直至减不了,有点贪心算法的味道。
除此之外,通过这道题还学到了abs的问题以及如何自己写一个abs函数。