(一)LeetCode29:两数相除
暴力做法是循环 x -= y,x为被除数,y为除数,减到 x 小于 y 为止,每减一次计数变量 ++,最后输出计数变量。
然而以上这种做法显然是会超时的!!!
高级解法是二进制移位倍增,其实这也是计算机实现除法的本质。二进制倍增的意思就是暴力的做法让y一个一个地增太慢了,每次应该大一点地向x逼近,那么怎么确定这个大呢?
我们可以,以y为基数,并定义一个数组,在小于x的情况,y倍增一倍后存入该数组,循环,直到该数大于x。这时我们每次在向x逼近的时候就可以从上面那个数组中最大的数向x逼近,逼近成功(逼近的数小于x),那么商就加上 2的该数在数组中索引的次方(为什么是2?因为y在倍增准备的时候是一次增加一倍的)即可。
第一种代码实现:使用long long
但现在lc已经规定假设只有int这一数据类型,所以这一解法是不成立的,但二进制的基本思路还是不变的。
实现的时候有两个难点:
1、 怎么产生倍增数组
2、不用乘法,商怎么加上 2的索引次方(这里使用了位运算,1 << i 的意义就是将1在二进制上向左移位 i 格,新移出的部分都会0,比如:1 << 3,实际上在二进制中就是0001 —> 1000,变为了2^3。从上面的例子可以看出对1做左移位,向左移动几位后其值就正好是2的 几次方)
若想进一步了解“位运算”,可以参考文章:
c++位运算中最常用的两种操作
【注】这里由于可能会出现溢出,即如果i==31时,2^31就已经超出了int的数据范围了,所以在1后面加上了 ll (long long)防止溢出。这是c++11中的新特性,将ll或LL加在某个数字的后面,可以将其提升为long long类型,也可以防止在后面的计算中溢出!!!
代码示例:
class Solution {
public:
int divide(int x, int y) {
typedef long long LL; // type + def : 由于long long写起来比较长,所以可以重新定义一下。定义的时候你可以读作:type long long def LL,这样更好记忆,long long要放在前面,LL 要放在后面
vector<LL> exp; // 用于存二进制倍增数组
bool is_minus = false;
if(x < 0 && y > 0 || x > 0 && y < 0) is_minus = true;
LL a = labs((LL)x), b = labs((LL)y); // x可能是-2^31,除以一个正数可能就溢出了(但现在题目规定了不能用long long,假设只有int存)
// 现在开始生成二进制倍增的前提,在小于a的前提下,每次都倍增一次
// 到时候减的时候,从大向小减
for(LL i = b; i <= a; i = i + i) exp.push_back(i); // 注意存入方式是push_back,注意是可以取到等于a的,最好的情况就是和a相等,这样就能够整除了
LL res = 0;
for(int i = exp.size() - 1; i >= 0; i --) {
// 从大往小去减
while(a >= exp[i]) {
// 这里while的时间复杂度为O(1),因为只会执行一次,其实这里用if也行,只是while更好理解
a -= exp[i];
// 下面是关键代码:如何获取exp[i]对应多少商?
res += 1ll << i; // 为什么加上2的i次方,因为假设上面条件成立,那么x时减去了2个i次方的y成立,商则是要加上2的i次方
// 2的i次方注意会溢出,不过这里防止溢出的方式还是很难理解
}