根据「前言」部分的讨论,我们记被除数为 X,除数为 Y,并且 X 和 Y 都是负数。我们需要找出 X/Y 的结果 Z。Z 一定是正数或 0。
根据除法以及余数的定义,我们可以将其改成乘法的等价形式,即:
Z \times Y \geq X > (Z+1) \times Y
因此,我们可以使用二分查找的方法得到 Z,即找出最大的 Z 使得 Z \times Y \geq X 成立。
由于我们不能使用乘法运算符,因此我们需要使用「快速乘」算法得到 Z \times Y 的值。「快速乘」算法与「快速幂」类似,前者通过加法实现乘法,后者通过乘法实现幂运算。「快速幂」算法可以参考「50. Pow(x, n)」的官方题解 ,「快速乘」算法只需要在「快速幂」算法的基础上,将乘法运算改成加法运算即可。
在实现「快速乘」时,我们需要使用加法运算,然而较大的 Z 也会导致加法运算溢出。例如我们要判断 A + B 是否小于 C 时(其中 A, B, C 均为负数),A + B 可能会产生溢出,因此我们必须将判断改为 A < C - B 是否成立。由于任意两个负数的差一定在 [-2^{31} + 1, 2^{31} - 1] 范围内,这样就不会产生溢出。
classSolution { public: intdivide(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) { return0; } // 一般情况,使用二分查找 // 将所有的正数取相反数,这样就只需要考虑一种情况 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) { returnfalse; } result += add; } if (z != 1) { // 需要保证 add + add >= x if (add < x - add) { returnfalse; } add += add; } // 不能使用除法 z >>= 1; } returntrue; }; 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; } }
classSolution { publicintdivide(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; } // 考虑被除数为 0 的情况 if (a == 0) { return0; } // 一般情况,使用二分查找 // 将所有的正数取相反数,这样就只需要考虑一种情况 booleanrev=false; if (a > 0) { a = -a; rev = !rev; } if (b > 0) { b = -b; rev = !rev; } intleft=1, right = Integer.MAX_VALUE, ans = 0; while (left <= right) { // 注意溢出,并且不能使用除法 intmid= left + ((right - left) >> 1); booleancheck= quickAdd(b, mid, a); if (check) { ans = mid; // 注意溢出 if (mid == Integer.MAX_VALUE) { break; } left = mid + 1; } else { right = mid - 1; } }
return rev ? -ans : ans; }
// 快速乘 publicbooleanquickAdd(int y, int z, int x) { // x 和 y 是负数,z 是正数 // 需要判断 z * y >= x 是否成立 intresult=0, add = y; while (z != 0) { if ((z & 1) != 0) { // 需要保证 result + add >= x if (result < x - add) { returnfalse; } result += add; } if (z != 1) { // 需要保证 add + add >= x if (add < x - add) { returnfalse; } add += add; } // 不能使用除法 z >>= 1; } returntrue; } }
publicclassSolution { publicintDivide(int a, int b) { // 考虑被除数为最小值的情况 if (a == int.MinValue) { if (b == 1) { returnint.MinValue; } if (b == -1) { returnint.MaxValue; } } // 考虑除数为最小值的情况 if (b == int.MinValue) { return a == int.MinValue ? 1 : 0; } // 考虑被除数为 0 的情况 if (a == 0) { return0; } // 一般情况,使用二分查找 // 将所有的正数取相反数,这样就只需要考虑一种情况 bool rev = false; if (a > 0) { a = -a; rev = !rev; } if (b > 0) { b = -b; rev = !rev; } int left = 1, right = int.MaxValue, ans = 0; while (left <= right) { // 注意溢出,并且不能使用除法 int mid = left + ((right - left) >> 1); bool check = quickAdd(b, mid, a); if (check) { ans = mid; // 注意溢出 if (mid == int.MaxValue) { break; } left = mid + 1; } else { right = mid - 1; } }
return rev ? -ans : ans; }
// 快速乘 publicboolquickAdd(int y, int z, int x) { // x 和 y 是负数,z 是正数 // 需要判断 z * y >= x 是否成立 int result = 0, add = y; while (z != 0) { if ((z & 1) != 0) { // 需要保证 result + add >= x if (result < x - add) { returnfalse; } result += add; } if (z != 1) { // 需要保证 add + add >= x if (add < x - add) { returnfalse; } add += add; } // 不能使用除法 z >>= 1; } returntrue; } }
# 考虑被除数为最小值的情况 if a == INT_MIN: if b == 1: return INT_MIN if b == -1: return INT_MAX # 考虑除数为最小值的情况 if b == INT_MIN: return1if a == INT_MIN else0 # 考虑被除数为 0 的情况 if a == 0: return0 # 一般情况,使用二分查找 # 将所有的正数取相反数,这样就只需要考虑一种情况 rev = False if a > 0: a = -a rev = not rev if b > 0: b = -b rev = not rev
# 快速乘 defquickAdd(y: int, z: int, x: int) -> bool: # x 和 y 是负数,z 是正数 # 需要判断 z * y >= x 是否成立 result, add = 0, y while z > 0: if (z & 1) == 1: # 需要保证 result + add >= x if result < x - add: returnFalse result += add if z != 1: # 需要保证 add + add >= x if add < x - add: returnFalse add += add # 不能使用除法 z >>= 1 returnTrue left, right, ans = 1, INT_MAX, 0 while left <= right: # 注意溢出,并且不能使用除法 mid = left + ((right - left) >> 1) check = quickAdd(b, mid, a) if check: ans = mid # 注意溢出 if mid == INT_MAX: break left = mid + 1 else: right = mid - 1
// 快速乘 // x 和 y 是负数,z 是正数 // 判断 z * y >= x 是否成立 funcquickAdd(y, z, x int)bool { for result, add := 0, y; z > 0; z >>= 1 { // 不能使用除法 if z&1 > 0 { // 需要保证 result + add >= x if result < x-add { returnfalse } result += add } if z != 1 { // 需要保证 add + add >= x if add < x-add { returnfalse } add += add } } returntrue }
funcdivide(a, b int)int { if a == math.MinInt32 { // 考虑被除数为最小值的情况 if b == 1 { return math.MinInt32 } if b == -1 { return math.MaxInt32 } } if b == math.MinInt32 { // 考虑除数为最小值的情况 if a == math.MinInt32 { return1 } return0 } if a == 0 { // 考虑被除数为 0 的情况 return0 }
// 一般情况,使用二分查找 // 将所有的正数取相反数,这样就只需要考虑一种情况 rev := false if a > 0 { a = -a rev = !rev } if b > 0 { b = -b rev = !rev }
ans := 0 left, right := 1, math.MaxInt32 for left <= right { mid := left + (right-left)>>1// 注意溢出,并且不能使用除法 if quickAdd(b, mid, a) { ans = mid if mid == math.MaxInt32 { // 注意溢出 break } left = mid + 1 } else { right = mid - 1 } } if rev { return -ans } return ans }
var divide = function(a, b) { constMAX_VALUE = 2 ** 31 - 1, MIN_VALUE = -(2 ** 31); // 考虑被除数为最小值的情况 if (a === MIN_VALUE) { if (b === 1) { returnMIN_VALUE; } if (b === -1) { returnMAX_VALUE; } } // 考虑除数为最小值的情况 if (b === MIN_VALUE) { return a === MIN_VALUE ? 1 : 0; } // 考虑被除数为 0 的情况 if (a === 0) { return0; } // 一般情况,使用二分查找 // 将所有的正数取相反数,这样就只需要考虑一种情况 let rev = false; if (a > 0) { a = -a; rev = !rev; } if (b > 0) { b = -b; rev = !rev; } let left = 1, right = MAX_VALUE, ans = 0; while (left <= right) { // 注意溢出,并且不能使用除法 const mid = left + ((right - left) >> 1); const check = quickAdd(b, mid, a); if (check) { ans = mid; // 注意溢出 if (mid === MAX_VALUE) { break; } left = mid + 1; } else { right = mid - 1; } }
return rev ? -ans : ans; }
// 快速乘 constquickAdd = (y, z, x) => { // x 和 y 是负数,z 是正数 // 需要判断 z * y >= x 是否成立 let result = 0, add = y; while (z !== 0) { if ((z & 1) !== 0) { // 需要保证 result + add >= x if (result < x - add) { returnfalse; } result += add; } if (z !== 1) { // 需要保证 add + add >= x if (add < x - add) { returnfalse; } add += add; } // 不能使用除法 z >>= 1; } returntrue; };
复杂度分析
时间复杂度:O(\log^2 C),其中 C 表示 32 位整数的范围。二分查找的次数为 O(\log C),其中的每一步我们都需要 O(\log C) 使用「快速乘」算法判断 Z \times Y \geq X 是否成立,因此总时间复杂度为 O(\log^2 C)。
classSolution { public: intdivide(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) { return0; } // 一般情况,使用类二分查找 // 将所有的正数取相反数,这样就只需要考虑一种情况 bool rev = false; if (a > 0) { a = -a; rev = !rev; } if (b > 0) { b = -b; rev = !rev; }
vector<int> candidates = {b}; // 注意溢出 while (candidates.back() >= a - candidates.back()) { candidates.push_back(candidates.back() + candidates.back()); } int ans = 0; for (int i = candidates.size() - 1; i >= 0; --i) { if (candidates[i] >= a) { ans += (1 << i); a -= candidates[i]; } }
publicclassSolution { publicintDivide(int a, int b) { // 考虑被除数为最小值的情况 if (a == int.MinValue) { if (b == 1) { returnint.MinValue; } if (b == -1) { returnint.MaxValue; } } // 考虑除数为最小值的情况 if (b == int.MinValue) { return a == int.MinValue ? 1 : 0; } // 考虑被除数为 0 的情况 if (a == 0) { return0; } // 一般情况,使用类二分查找 // 将所有的正数取相反数,这样就只需要考虑一种情况 bool rev = false; if (a > 0) { a = -a; rev = !rev; } if (b > 0) { b = -b; rev = !rev; }
IList<int> candidates = new List<int>(); candidates.Add(b); int index = 0; // 注意溢出 while (candidates[index] >= a - candidates[index]) { candidates.Add(candidates[index] + candidates[index]); ++index; } int ans = 0; for (int i = candidates.Count - 1; i >= 0; --i) { if (candidates[i] >= a) { ans += 1 << i; a -= candidates[i]; } }
# 考虑被除数为最小值的情况 if a == INT_MIN: if b == 1: return INT_MIN if b == -1: return INT_MAX # 考虑除数为最小值的情况 if b == INT_MIN: return1if a == INT_MIN else0 # 考虑被除数为 0 的情况 if a == 0: return0 # 一般情况,使用类二分查找 # 将所有的正数取相反数,这样就只需要考虑一种情况 rev = False if a > 0: a = -a rev = not rev if b > 0: b = -b rev = not rev candidates = [b] # 注意溢出 while candidates[-1] >= a - candidates[-1]: candidates.append(candidates[-1] + candidates[-1]) ans = 0 for i inrange(len(candidates) - 1, -1, -1): if candidates[i] >= a: ans += (1 << i) a -= candidates[i]
funcdivide(a, b int)int { if a == math.MinInt32 { // 考虑被除数为最小值的情况 if b == 1 { return math.MinInt32 } if b == -1 { return math.MaxInt32 } } if b == math.MinInt32 { // 考虑除数为最小值的情况 if a == math.MinInt32 { return1 } return0 } if a == 0 { // 考虑被除数为 0 的情况 return0 }
// 一般情况,使用二分查找 // 将所有的正数取相反数,这样就只需要考虑一种情况 rev := false if a > 0 { a = -a rev = !rev } if b > 0 { b = -b rev = !rev }
candidates := []int{b} for y := b; y >= a-y; { // 注意溢出 y += y candidates = append(candidates, y) }
ans := 0 for i := len(candidates) - 1; i >= 0; i-- { if candidates[i] >= a { ans |= 1 << i a -= candidates[i] } } if rev { return -ans } return ans }
var divide = function(a, b) { constMAX_VALUE = 2 ** 31 - 1, MIN_VALUE = -(2 ** 31); // 考虑被除数为最小值的情况 if (a === MIN_VALUE) { if (b === 1) { returnMIN_VALUE; } if (b === -1) { returnMAX_VALUE; } } // 考虑除数为最小值的情况 if (b === MIN_VALUE) { return a === MIN_VALUE ? 1 : 0; } // 考虑被除数为 0 的情况 if (a === 0) { return0; } // 一般情况,使用类二分查找 // 将所有的正数取相反数,这样就只需要考虑一种情况 let rev = false; if (a > 0) { a = -a; rev = !rev; } if (b > 0) { b = -b; rev = !rev; }
const candidates = [b]; let index = 0; // 注意溢出 while (candidates[index] >= a - candidates[index]) { candidates.push(candidates[index] + candidates[index]); ++index; } let ans = 0; for (let i = candidates.length - 1; i >= 0; --i) { if (candidates[i] >= a) { ans += 1 << i; a -= candidates[i]; } }
return rev ? -ans : ans; };
复杂度分析
时间复杂度:O(\log C),即为二分查找需要的时间。方法二时间复杂度优于方法一的原因是:方法一的每一步二分查找都需要重新计算 Z \times Y 的值,需要 O(\log C) 的时间复杂度;而方法二的每一步的权重都是 2 的幂,在二分查找开始前就都是已知的值,因此我们可以在 O(\log C) 的时间内,一次性将它们全部预处理出来。