001. 整数除法:
给定两个整数 a 和 b ,求它们的除法的商 a/b ,要求不得使用乘号 ‘*’、除号 ‘/’ 以及求余符号 ‘%’ 。
一些知识点和思路
第一题,easy题,狠狠来了个下马威。
- 首先是 “被除数/除数”
- 关于溢出的情况,可以对除数与被除数分别按照他们的临界值进行考虑
- 快速乘原理基于二进制的特性
关于快速乘中位操作的一些知识点
快速乘的代码
auto quickAdd = [](int y, int z, int x) {
// x 和 y 是负数,z 是正数
// 需要判断 z * y >= x 是否成立
int result = 0, add = y;
while (z)
{
if (z & 1)
{
// 需要保证 result + add >= x
if (result < x - add) {
return false;
}
result += add;
}
if (z != 1)
{
// 需要保证 add + add >= x
if (add < x - add) {
return false;
}
add += add;
}
// 不能使用除法
z >>= 1;
}
return true;
};
变量result和add
在这段代码中,result和add变量的含义如下:
result:表示乘法的结果,即最终计算得到的乘积。
add:表示乘法中的一个因子,用于将其加入到最终的结果中。
在快速乘法算法中,通过不断将add乘以2,并根据z的二进制表示中的每一位判断是否需要将add加入到result中。
当z的某一位为1时,表示需要将add加入到result中;当z的某一位为0时,不需要加入。
在循环中,根据z的最低位判断是否需要将add加入到result中。
如果需要加入,则先判断result + add是否小于x,如果是,则返回false,表示乘法运算失败。
然后将add加入到result中,即result += add。
接下来,判断z是否等于1,如果不等于1,则需要将add乘以2。
在乘法之前,先判断add + add是否小于x,如果是,则返回false,表示乘法运算失败。
然后将add更新为add + add。
最后,将z右移1位,相当于将二进制表示中的最低位去掉,继续下一轮循环。
通过这样的循环和判断,可以实现快速乘法运算,并且避免了溢出的问题。
一个整数&1表示什么:表示最低一位,一般表示奇偶性
一个整数右移1位代表什么:相当于除以2,右移位数相当于除以2的指定次数
同理左移相当于乘以2的指定次数
为什么要判断z的最低位是否为1和是否等于1
判断z的最低位是否为1和是否等于1的目的是为了确定乘法运算的次数和乘法的基数。
在快速乘法算法中,我们将z表示为二进制形式,
从最低位开始,每一位代表了乘法中的一个因子。
如果某一位为1,表示需要将对应的因子加入到最终的结果中;
如果某一位为0,表示不需要将对应的因子加入到结果中。
因此,判断z的最低位是否为1,可以确定是否需要将add加入到result中。
如果最低位为1,则需要加入;如果最低位为0,则不需要加入。
而判断z是否等于1,可以确定是否还有其他位需要进行乘法运算。
如果z等于1,说明已经处理完了所有的位,不需要再进行乘法运算;
如果z不等于1,说明还有其他位需要处理,需要将add乘以2。
这样,通过判断z的最低位和是否等于1,可以在循环中正确地进行乘法运算,避免了不必要的计算和溢出的问题。
一些快速乘的具体示例
当我们具体代入数值来理解快速乘法的工作原理时,可以考虑以下示例:
假设我们要计算 z * y 是否大于等于 x,其中 z = 5,y = -3,x = -14。
我们可以使用快速乘法来判断 z * y 是否大于等于 x,具体的代码如下:
#include <iostream>
bool quickMultiply(int z, int y, int x) {
int result = 0, add = y;
while (z) {
if (z & 1) {
if (result < x - add) {
return false;
}
result += add;
}
if (z != 1) {
if (add < x - add) {
return false;
}
add += add;
}
z >>= 1;
}
return true;
}
int main() {
int z = 5, y = -3, x = -14;
bool result = quickMultiply(z, y, x);
std::cout << "Is " << z << " * " << y << " >= " << x << "? " << std::boolalpha << result << std::endl;
return 0;
}
在这个示例中,我们使用 quickMultiply 函数来判断 z * y 是否大于等于 x。根据快速乘法的原理,我们会在循环中根据 z 的二进制表示的每一位来判断是否需要将 add 加入到 result 中。
具体的计算过程如下:
初始时,result = 0,add = -3。
第一轮循环,z = 5,z & 1 = 1,表示需要将 add 加入到 result 中。
result + add = 0 + (-3) = -3。
result < x - add 不成立,继续执行。
result += add,此时 result = -3。
第二轮循环,z = 2,z & 1 = 0,表示不需要将 add 加入到 result 中。
add + add = -3 + (-3) = -6。
add < x - add 不成立,继续执行。
add += add,此时 add = -6。
第三轮循环,z = 1,z & 1 = 1,表示需要将 add 加入到 result 中。
result + add = -3 + (-6) = -9。
result < x - add 不成立,继续执行。
result += add,此时 result = -9。
第四轮循环,z = 0,循环结束。
最终,result = -9。由于 -9 不大于等于 -14,所以 z * y 不大于等于 x。
因此,程序输出结果为:Is 5 * -3 >= -14? false。
通过这个具体的例子,我们可以更直观地理解快速乘法函数的作用,即判断两个数的乘积是否大于等于给定的数。
官方答案
class Solution {
public:
int divide(int a, int b) {
// 考虑被除数为最小值的情况
if (a == INT_MIN) {
if (b == 1) {
return INT_MIN;
}
if (b == -1) {
return INT_MAX;
}
}
// 考虑除数为最小值的情况
if (b == INT_MIN) {
return a == INT_MIN ? 1 : 0;
}
// 考虑被除数为 0 的情况
if (a == 0) {
return 0;
}
// 一般情况,使用二分查找
// 将所有的正数取相反数,这样就只需要考虑一种情况
bool rev = false;
if (a > 0) {
a = -a;
rev = !rev;
}
if (b > 0) {
b = -b;
rev = !rev;
}
// 快速乘
auto quickAdd = [](int y, int z, int x) {
// x 和 y 是负数,z 是正数
// 需要判断 z * y >= x 是否成立
int result = 0, add = y;
while (z)
{
if (z & 1)
{
// 需要保证 result + add >= x
if (result < x - add) {
return false;
}
result += add;
}
if (z != 1)
{
// 需要保证 add + add >= x
if (add < x - add) {
return false;
}
add += add;
}
// 不能使用除法
z >>= 1;
}
return true;
};
int left = 1, right = INT_MAX, ans = 0;
while (left <= right)
{
// 注意溢出,并且不能使用除法
int mid = left + ((right - left) >> 1);
bool check = quickAdd(b, mid, a);
if (check)
{
ans = mid;
// 注意溢出
if (mid == INT_MAX) {
break;
}
left = mid + 1;
}
else {
right = mid - 1;
}
}
return rev ? -ans : ans;
}
};
当前的一些疑问:
快乘法是用来判断当前y*z是否大于等于x的,那如果当前的值不是即false,不是表示当前值小,正确的应该在右边,则应该是left = mid + 1;当返回true的时候表示大了,应该right = mid - 1;吗,不理解,整体都不是太理解,但是知道了一些位运算的操作,不要让第一题耽搁太久,留着等有更多基础再重做
更新 终于理解了
class Solution {
public:
int divide(int dividend, int divisor) {
//会溢出的情况
if(dividend == INT_MIN && divisor == -1)
{
return INT_MAX;
}
// 特殊情况,无需计算,直接返回
if (dividend == 0 || divisor == 1) {
return dividend;
} else if (divisor == -1) {
return -dividend;
}
// 由于(-2^31) 转换为正数会溢出,但是任意正数转换为负数都不会溢出
// 故,记录负数的个数,并将正数转换为负数方便统一计算
int negative = 0;
if(dividend > 0)
{
dividend = -dividend;
negative++;
}
if(divisor > 0)
{
divisor = -divisor;
negative++;
}
//开始用位运算找被除数中有几个除数 首先找到一个最大的shiftshift 比如 61 /3 那首先61-3*2^4=61-48。但3*2^5 = 96 所以maxshift = 4
//然后剩下的61-48=13。13里再去找有几个2
int res = findAllShift(dividend, divisor);
return negative == 1?-res:res;
}
int findAllShift(int dividend, int divisor)
{
int res = 0;
//找到最大的shift
int shift = 0;
//注意这里的除数和被除数都是负数 要不我给换回来吧 求shift不是都一样的嘛 -64 -3
int temp = divisor;
while(temp > dividend)
{
temp <<= 1;//这相当于 -3 * 2^1
shift++;
}
//得到shift 开始从shift往下找
while(dividend <= divisor)
{
//dividend一直会右移变小
while(dividend > (divisor << shift))
{
shift--;
}
dividend -= divisor<<shift;
res += (1 << shift);
}
return res;
}
};