【第04题】给定 a 和 b,问 a 能否被 b 整除 | if 语句 和 条件运算符的应用
主要知识点
嗯,别看了,这里确实什么都没有,直接上题
习题
1. Leetcode 29. 两数相除
题目描述
给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
限制:
- 被除数和除数均为 32 位有符号整数。
- 除数不为 0。
- 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [ − 2 31 , 2 31 − 1 ] [−2^{31}, 2^{31} − 1] [−231,231−1]。本题中,如果除法结果溢出,则返回 2 31 − 1 2^{31} − 1 231−1。
初见
说到除法的替代,第一反应肯定是通过循环将其降级为减法 😄 不出所料,写代码,提交,超时一气呵成~
那么存在限制条件的情况下,当然又将思路回到了位运算上。这里让我们暂时忽略最后一个限制条件,此时我们要做的是利用位运算,每次判断时将除数
×
2
\times2
×2,提升循环减法的搜索速度:
int divide(int dividend, int divisor) {
int sign = (dividend>>31 & 0x1) ^ (divisor>>31 & 0x1); // 符号位,同号为 0, 异号为 1
if (sign) {
sign = -1;
} else {
return 1;
}
long long divd = abs(dividend); // 这里我们用 long long 方便计算
long long divi = abs(divisor);
long long oriDiv = divi;
long long result(0);
if (divi == divd) {
return sign * 1;
}
while (divi <= divd) {
int bitMove(0);
while (divi <= divd) {
divi <<= 1; // divisor * 2
bitMove++;
}
bitMove--;
divd = divd - (divi>>1); // 更新被除数,divd - 2^bitMove * divi
divi = oriDiv; // 重置 divi
if (bitMove > 0) { // 更新结果
result += (unsigned int)0x1 << bitMove;
} else {
result++;
}
}
result *= sign;
if (result > 0x7FFFFFFF) { // 保证不越界
return 0x7FFFFFFF;
} else {
return result;
}
}
思路
回到最后一个限制条件上,这里要求我们整个运算过程中只用 32 位有符号整数,那么可以换一种搜索方式,加快搜索速度。此时可以采用 二分查找(divisor 与 dividend 均为负数,计算 dividend / divisor):
- 令
l = 1,r = intMax; - 若
l <= r,取mid = l + ((r - l) >> 1), 否则跳至步骤 4; - 计算
result = mid * divisor,若result < divisor则令r = mid - 1,否则记录mid值并令l = mid + 1,重复步骤 2, 3; - 返回结果
上述描述中的乘法可以参考习题1实现快速乘法。描述中没有涉及边界值的处理,但在代码中需要对可能涉及到越界的情况进行判断与处理。该方法为二分查找的具体应用,模板可以参考资料
代码
int divide(int dividend, int divisor) {
bool sameSign = (dividend >> 31 & 0x1) ^ (divisor >> 31 & 0x1);
if (dividend == 0) { // 被除数为 0 时不能依靠快速乘计算结果
return 0;
}
if (dividend == INT_MIN) { // 处理边界值
if (divisor == 1) {
return INT_MIN;
}
if (divisor == -1) {
return INT_MAX;
}
}
if (dividend > 0) { // 正数转到负数扩大动态范围,方便统一处理边界值
dividend = -dividend;
}
if (divisor > 0) {
divisor = -divisor;
}
// 二分查找
int l(1), r(INT_MAX), ans(0);
while (l <= r) {
int mid = l + ((r - l) >> 1);
bool isLess = checkIfLessC(mid, divisor, dividend); // 查找条件
if (isLess) {
r = mid - 1; // 若没有 mid +- 1,则不能搜索 [l, r] 范围内的所有数
} else {
ans = mid;
if (mid == INT_MAX) { // 防止 +1 后越界
return INT_MAX;
}
l = mid + 1;
}
}
return sameSign? -ans: ans;
}
bool checkIfLessC(int mid, int divisor, int dividend) {
// 若 mid * divisor < dividend 返回 true
int ans(0);
while (mid != 0) {
if (mid & 0x1) {
if (ans < dividend - divisor) { // 防止 ans + divisor 越界
return true;
}
ans += divisor;
}
mid >>= 1;
if (mid != 0) {
if (divisor < dividend) { // 提前结束循环
return true;
}
// 防止 divisor + divisor 越界,此时若越界则 mid * divisor < dividend
if (divisor < INT_MIN / 2) {
return true;
}
divisor += divisor;
}
}
return false;
}
总结
- 位运算在限制条件下真的很好用
- 二分查找算法
- 两种基础格式,一种搜索 [ L , R ) [L, R) [L,R),一种搜索 [ L , R ] [L, R] [L,R]
- 要注意算法中边界条件的处理
- 负数动态范围更大,因此在有符号数的计算中,为处理溢出可以将正数转为负数,进行统一处理
本文介绍了如何通过位运算和二分查找算法,避开乘除和mod运算符,解决LeetCode 29题的整数除法问题。着重讨论了如何在有限精度内处理溢出,以及如何利用负数动态范围优化搜索。
277

被折叠的 条评论
为什么被折叠?



