题目描述:
解法
var divide = function (dividend, divisor) {
const sign = (dividend > 0) ^ (divisor > 0) ? -1 : 1;
const MAX_INT = 2 ** 31 - 1;
const MIN_INT = -(2 ** 31);
dividend = Math.abs(dividend);
divisor = Math.abs(divisor);
if (dividend === 0 || dividend < divisor) {
return 0;
}
let left = 1;
let right = dividend;
let result = 0;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (mid * divisor === dividend) {
result = mid;
break;
} else if (mid * divisor < dividend) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
result = sign === 1 ? Math.min(result, MAX_INT) : -Math.max(result, MIN_INT);
return result;
};
console.log(divide(-2147483648, -1)); // 输出 2147483647
console.log(divide(10, 3)); // 输出:3
console.log(divide(7,-3)) // 输出:-2
思考过程:首先 确定题目要求:
题目要求:
- 不使用 乘法、除法和取余运算
- 要求向下取整
- 有正负之分
- 除数不能为0,环境只能存储32位有符号整数
解题过程
1.根据题目要求 先确定正负符号
- 都为正 答案为正
- 都为负 答案为正
- 一正一负 答案为负
就这个问题我觉得常用的三元表达式似乎已经不太能优雅的解决这个问题,这里我采用了位运算符中的异或运算符在结合三元表达式解决符号位
^ 是位运算符的一种--异或运算符,结果的表现形式为:相同为0,不同为1
表达式 A ^ B
eg: 1 ^ 1 ===> 0
eg: 1 ^ 0 ===> 1
因此我们可以使用这种方式先确定符号位
const sign = (dividend > 0) ^ (divisor > 0) ? -1 : 1;
2.定义返回的结果
let result = 0;
3.使用 Math.abs()确保在运算过程中的符号问题
dividend = Math.abs(dividend);
divisor = Math.abs(divisor);
4.开始真正的逻辑判断
- 首先我们要判断被除数大于除数
- 定义一个临时变量 tempDivisor 初始值为除数,这个临时变量在后面每一次while循环中会倍增
- 还定义了一个变量multiple,初始值为1。这个变量将用于记录除法的倍数。
- 在循环内部开启另一个while循环,条件是被除数大于等于tempDivisor的两倍。这个循环的目的是通过倍增tempDivisor来加快除法的速度
- 当内部循环结束后,我们更新被除数和结果。被除数减去tempDivisor的值,相当于将tempDivisor从被除数中减去。结果result加上multiple的值,相当于将multiple加到结果中。
- 最后根据符号位和环境储存变量的上限和下限 得出结果
相关代码:
while (dividend >= divisor) {
let tempDivisor = divisor;
let multiple = 1;
while (dividend >= (tempDivisor << 1)) {
tempDivisor <<= 1;
multiple <<= 1;
}
dividend -= tempDivisor;
result += multiple;
}
result = sign === 1 ? Math.min(result, MAX_INT) : -Math.max(result, MIN_INT);
<< 是js位运算符的一种,
先说一个简单的结论 a << b ===> a*2^b
相当于就是 把a往右边移动b位(b在二进制下有多少位就移动多少位(多少个0)) 可以使用(x).toString(y) 的方式把x转化为y进制下的结果
代码解释
const a = 5; // 00000000000000000000000000000101
const b = 2; // 00000000000000000000000000000010
console.log(a << b); // 00000000000000000000000000010100
上述代码的核心是 这段代码中的两层循环是为了实现除法运算。
外层循环的条件是被除数(dividend)大于等于除数(divisor)。只要这个条件满足,就意味着我们还可以继续进行除法运算。
内层循环的目的是找到最大的除数的倍数,使得这个倍数乘以除数仍然不大于被除数。这个倍数就是我们当前可以从被除数中减去的最大的除数的倍数。在每次内层循环中,我们都将除数和倍数翻倍,直到除数的两倍大于被除数。
这样,外层循环每次都会找到一个最大的可以从被除数中减去的除数的倍数,并将其从被除数中减去,同时将对应的倍数加到结果中。这就完成了一次除法运算。然后,外层循环会继续执行,直到被除数小于除数为止。
这种方法的优点是可以在每次循环中,将被除数减半,从而大大减少循环的次数,提高了算法的效率。
运行代码
在vscode中运行正确
现在我们迁移到力扣中看看效果怎么样
测试用例1--通过
测试用例2--通过
提交
我丢超出时间限制,为什么会出现这个原因,我猜想是我们 内层循环执行太多次!
验证猜想: 在循环外定义一个变量在内层循环中每次先递增1,然后在打印,果然出现问题! 这里手动停止了代码执行
解决问题
鼓捣许久,这个问题我并没有想到解决方法,麻烦大家看看这个问题该怎么解?
这种写法在大数相除时没有得到很好的解决办法,想到大数,我想起了二分查找的思想,这算法题 我们用二分查找的的方法来试试
代码和我上面提到的差不多,只是在主要逻辑上进行了修改,比较简单,就不一一给出注释了
const sign = (dividend > 0) ^ (divisor > 0) ? -1 : 1; // 位运算,异或,相同为0,不同为1
const MAX_INT = 2 ** 31 - 1;
const MIN_INT = -(2 ** 31);
dividend = Math.abs(dividend);
divisor = Math.abs(divisor);
if (dividend === 0 || dividend < divisor) {
return 0;
}
let left = 1;
let right = dividend;
let result = 0;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (mid * divisor === dividend) {
result = mid;
break;
} else if (mid * divisor < dividend) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
result = sign === 1 ? Math.min(result, MAX_INT) : -Math.max(result, MIN_INT);
return result;
};
咱们就不一一的过测试用列了,直接在力扣上提交看效果。
测试通过!
最后遗漏问题:
TODO:在第一种解法中如果出现大数除以小数会出现内层循环太多次的问题,导致运行不通过!
麻烦大家提出你们的想法,帮助菜鸡我解决这个问题,感恩!感恩!