题目
给定两个整数 a
和 b
,求它们的除法的商 a/b
,要求不得使用乘号 '*'
、除号 '/'
以及求余符号 '%'
。
注意:
- 整数除法的结果应当截去(
truncate
)其小数部分,例如:truncate(8.345) = 8
以及truncate(-2.7335) = -2
- 假设我们的环境只能存储 32 位有符号整数,其数值范围是
[−231, 231−1]
。本题中,如果除法结果溢出,则返回231 − 1
示例 1:
输入:a = 15, b = 2 输出:7 解释:15/2 = truncate(7.5) = 7
示例 2:
输入:a = 7, b = -3 输出:-2 解释:7/-3 = truncate(-2.33333..) = -2
示例 3:
输入:a = 0, b = 1 输出:0
示例 4:
输入:a = 1, b = 1 输出:1
提示:
-231 <= a, b <= 231 - 1
b != 0
注意:本题与主站 29 题相同:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
链接:LCR 001. 两数相除 - 力扣(LeetCode)
题解
思路一:先考虑特殊的溢出情况(正数会少一个),
(1)被除数-2^31,除数-1,会溢出
(2)除数为-2^31,被除数除了自己,其他情况返回0.
(1)(2)考虑的都是最小值分别为除数和被除数,因为最大值不会溢出
(3)被除数为0,返回0
(4)a=Integer.max,b=1单独考虑,因为后面二分特殊考虑最右边值
一般情况,根据正负号有4中情况,因此除数被除数全部转化为负数运算
被除数X,除数Y,结果Z。将除法转换为等价的乘法
要找结果:Z×Y≤X<(Z+1)×Y。二分法找最大的Z使Z×Y≤X成立,又xy为负数,因此找最大的Z使Z×Y≥X
从1到2^31-1区间进行二分查找,查不到的话就是0。可能出现的2^31已经被特殊处理
由于限制了“*”,因此考虑快速乘(类似快速幂),即用多个加法来代替乘法
(1)暴力O(n),直接for循环。
(2)分治,2^10即两个2^5,根据奇偶递归,主函数处理边界情况及负数情况。O(logn)
(3)迭代,比如77可以分成1+4+8+64,即二进制的分解,77对应1001101.可以迭代相乘
注意代码过程中使用正数想会好想一点,但是由于x,y是负数,因此有时候需要改变不等号方向。
代码:
class Solution {
public int divide(int a, int b) {
if (a == Integer.MIN_VALUE) {
if (b == 1) return Integer.MIN_VALUE;
if (b == -1) return Integer.MAX_VALUE;
}
if (b == Integer.MIN_VALUE)
return a == Integer.MIN_VALUE ? 1 : 0;
if (a == 0) return 0;
//如果mid是max的话,正常是right-1,界边max右边没值了,因此特殊处理最右边的值
if(a==Integer.MAX_VALUE&&b==1)return Integer.MAX_VALUE;
// 一般情况,使用二分查找
// 将所有的正数取相反数,这样就只需要考虑一种情况
boolean rev = false;
if (a > 0) {a = -a;rev = !rev;}
if (b > 0) {b = -b;rev = !rev;}
int left = 1, right = Integer.MAX_VALUE, ans = 0,flag=0;
while (left < right) {
//找的是ZY<=X的最大Z,负数的话就是ZY>=X
// 注意溢出,并且不能使用除法
int mid = left + ((right - left) >> 1);
boolean check = quickAdd(b, mid, a);
if (!check) {
// 注意溢出
right = mid;
} else {
//if (mid == Integer.MAX_VALUE) break;
left = mid + 1;
}
}
ans=right-1;
return rev ? -ans : ans;
}
// 快速乘
public boolean quickAdd(int y, int z, int x) {
//xy是负数,z是正数
//从正数来说,判断y*z<=x是否成立。,负数考虑相反即y*z>=x
int result = 0, add = y;
while (z != 0) {
if ((z & 1) != 0) {
//正数来说,result+add>x返回flase,由于是负数,反一次
if (result < x - add) return false;
result += add;
}
if (z != 1) {
//add+add>x返回flase,负数
if (add < x - add) return false;
add += add;
}
z >>= 1;
}
return true;
}
}
tips:除了在考虑题目的特殊情况时会有溢出,同时也要考虑在快速乘过程中的Y×Z溢出,在分治的时候加判断。
用result<x-add而不是result+add来判断是为了防止溢出,减法不会溢出
思路二:类二分查找
特殊情况讨论同思路一
一般情况下,使用快速幂思想,使用List将过程中的值存储起来,先构造出完整的List,判断条件是最高位刚好小于等于被除数a,然后从最高位慢慢往进加,直到满足==
代码:
class Solution {
public int divide(int a, int b) {
if (a == Integer.MIN_VALUE) {
if (b == 1) return Integer.MIN_VALUE;
if (b == -1) return Integer.MAX_VALUE;
}
if (b == Integer.MIN_VALUE)
return a == Integer.MIN_VALUE ? 1 : 0;
if (a == 0) return 0;
//如果mid是max的话,正常是right-1,界边max右边没值了,因此特殊处理最右边的值
if(a==Integer.MAX_VALUE&&b==1)return Integer.MAX_VALUE;
// 一般情况,使用二分查找
// 将所有的正数取相反数,这样就只需要考虑一种情况
boolean rev = false;
if (a > 0) {a = -a;rev = !rev;}
if (b > 0) {b = -b;rev = !rev;}
List<Integer> record = new ArrayList<Integer>();
record.add(b);
int index=0,ans=0;
while(record.get(index)>=a-record.get(index)) {
record.add(record.get(index)+record.get(index));
index++;
}
for(int i=record.size()-1;i>=0;i--) {
if(record.get(i)>=a) {
int tmp=1<<i;
ans+=tmp;
a-=record.get(i);
}
}
return rev?-ans:ans;
}
}