*leetcode-29-两数相除-medium

题目描述

给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。

返回被除数 dividend 除以除数 divisor 得到的商。

示例 1:

输入: dividend = 10, divisor = 3
输出: 3

示例 2:

输入: dividend = 7, divisor = -3
输出: -2

说明:

被除数和除数均为 32 位有符号整数。
除数不为 0。
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。本题中,如果除法结果溢出,则返回 231 − 1。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/divide-two-integers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

  • 本人傻逼思路,没看到不给用除法,不过线下线上结果不一致也很迷
class Solution {
public:
    int divide(int dividend, int divisor) {
    	if(dividend == -2147483648&& divisor==-1){
            return 2147483647;
        }
        if(dividend==0){
            return 0;
        }
        if(abs(dividend)<abs(divisor)){
            return 0;
        }
        if(dividend==divisor){
            return 1;
        }
        int ret = 1;
        if(dividend>0&&divisor>0){
            while(ret*divisor<=dividend){
                ret++;
            }
            return ret-1;
        }else if(dividend>0&&divisor<0){
            divisor*=-1;
            while(ret*divisor<=dividend){
                ret++;
            }
            return -1*(ret-1);
        }else if(dividend<0&&divisor>0){
            dividend*=-1;
            while(ret*divisor<=dividend){
                ret++;
            }
            return -1*(ret-1);
        }else if(dividend<0&&divisor<0){
            dividend*=-1;
            divisor*=-1;
            while(ret*divisor<=dividend){
                ret++;
            }
            return ret-1;
        }
        return 0;
    }
        
        
};

二分

二分题解

题目分类提示了二分查找,那么按二分查找的思路去想就行了,既然要求不能使用乘除模运算,那么就让除数不断自加倍增,与被除数对比,倍增过头了就初始化为原始的除数值再次循环倍增,循环过程中被除数减去除数不断减小,直到小于除数为止,其过程就是把二分查找中的 mid=(left+right)/2 替换成了divisor*2,把 left 和 right 替换成了 divisor_tmp 和 divideng_tmp,这样理解就比较直观了。

class Solution {
    public int divide(int dividend, int divisor) {
        /** 除数为零就返回-1 按照测试样例的要求写的*/
        if (divisor==0)
            return -1;
        if (dividend==0)
            return 0;
        /** -2147483648, -1 这个测试样例的确没想到,结果翻车了*/
        if (dividend==Integer.MIN_VALUE && divisor==-1)
            return Integer.MAX_VALUE;
        /** 符号位的处理参考了大佬的异或处理方法*/
        boolean negetive= (dividend^ divisor)<0;
        /** div_count 是当前divisor_tmp相对于divisor的倍数 */
        int res=0, div_count=1;
        /** 因为值溢出之后边界问题处理太繁琐了,直接将数值转为long省去麻烦 */
        long dividend_tmp= Math.abs((long)dividend);
        long divisor_tmp= Math.abs((long)divisor);
        
        /** 按标准的二分查找代码模板写的 */
        while (dividend_tmp>= divisor_tmp) {
            dividend_tmp-= divisor_tmp;
            res+= div_count;
            
            if (dividend_tmp< Math.abs(divisor))
                break;
            
            /** divisor_tmp无法倍增时,就将其初始化为divisor绝对值,重新开始下一轮倍增*/
            if (dividend_tmp- divisor_tmp< divisor_tmp) {
                divisor_tmp= Math.abs(divisor);
                div_count=1;
                continue;
            } 
            
            /** 不断倍增divisor_tmp直到和dividend_tmp一样大*/
            divisor_tmp+= divisor_tmp;
            div_count+= div_count;
        }
        return negetive? 0-res: res;
    }
}
/*
作者:ao-ye-zhen-tou
链接:https://leetcode-cn.com/problems/divide-two-integers/solution/an-er-fen-cha-zhao-mo-ban-xie-de-by-ao-ye-zhen-tou/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/

数学方法

显然,用对数和幂运算性质,有 y/x=e^(lny-lnx)。先把除数和被除数取绝对值,divide = exp(log(dividend) - log(divisor))。如果取正后,溢出,则先把原来的数右移/左移一位,计算后,把结果再左移/右移一位。注意精度问题。


倍增思想

不使用乘法、除法和 mod 运算符来完成除法,那么就只能使用减法了,一种方法是先将除数和被除数都转换为正数,然后不断地从被除数中减去除数,减一个除数商就加一。但是这种方法显得太慢了,因此考虑在迭代中倍增除数,这样商就可以每次 +1 +2 +4 …。在某一时刻,当被除数小于除数的时候,把除数还原为初始值,然后继续倍增。

class Solution {
    public int divide(int dividend, int divisor) {
        int sign = (dividend ^ divisor) >> 31;
        long lDividend = Math.abs((long) dividend);
        long lDivisor = Math.abs((long) divisor);
        long res = 0;
        while (lDividend >= lDivisor){
            long tmp = lDivisor;
            long i = 1;
            while (lDividend >= tmp){
                lDividend -= tmp;
                res += i;
                i <<= 1;
                tmp <<= 1;
            }
        }
        if (sign == -1) res *= -1;
        if (res < Integer.MIN_VALUE) return Integer.MIN_VALUE;
        else if (res > Integer.MAX_VALUE) return Integer.MAX_VALUE;
        return (int)res;
        
    }
}

倍增的进阶版,指数增

     * 解题思路:这题是除法,所以先普及下除法术语
     * 商,公式是:(被除数-余数)÷除数=商,记作:被除数÷除数=...余数,是一种数学术语。
     * 在一个除法算式里,被除数、余数、除数和商的关系为:(被除数-余数)÷除数=商,记作:被除数÷除数=...余数,
     * 进而推导得出:商×除数+余数=被除数。
     *
     * 要求商,我们首先想到的是减法,能被减多少次,那么商就为多少,但是明显减法的效率太低
     *
     * 那么我们可以用位移法,因为计算机在做位移时效率特别高,向左移1相当于乘以2,向右位移1相当于除以2
     *
     * 我们可以把一个dividend(被除数)先除以2^n,n最初为31,不断减小n去试探,当某个n满足dividend/2^n>=divisor时,
     *
     * 表示我们找到了一个足够大的数,这个数*divisor是不大于dividend的,所以我们就可以减去2^n个divisor,以此类推
     *
     * 我们可以以100/3为例
     *
     * 2^n是1248...2^31这种数,当n为31时,这个数特别大,100/2^n是一个很小的数,肯定是小于3的,所以循环下来,
     *
     * 当n=5时,100/32=3, 刚好是大于等于3的,这时我们将100-32*3=4,也就是减去了323,接下来我们再处理4,同样手法可以再减去一个3
     *
     * 所以一共是减去了333,所以商就是33
     *
     * 这其中得处理一些特殊的数,比如divisor是不能为0的,Integer.MIN_VALUE和Integer.MAX_VALUE
     *

优美的代码

public int divide(int dividend, int divisor) {
        if (dividend == 0) {
            return 0;
        }
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE;
        }
        boolean negative;
        negative = (dividend ^ divisor) <0;//用异或来计算是否符号相异
        long t = Math.abs((long) dividend);
        long d= Math.abs((long) divisor);
        int result = 0;
        for (int i=31; i>=0;i--) {
            if ((t>>i)>=d) {//找出足够大的数2^n*divisor
                result+=1<<i;//将结果加上2^n
                t-=d<<i;//将被除数减去2^n*divisor
            }
        }
        return negative ? -result : result;//符号相异取反
    }

C++版本
这里限制了只能用int,因此没有使用long,增加了许多强转

#define LIMIT 0x80000000

static int divide(int dividend, int divisor) {
	if (dividend == 0) {
		return 0;
	}
	if (dividend == INT_MIN && divisor == -1) {
		return INT_MAX;
	}
	bool negative;
	negative = (dividend ^ divisor) < 0;//用异或来计算是否符号相异
	unsigned int t = dividend == INT_MIN ? LIMIT : abs(dividend);
	unsigned int d = divisor == INT_MIN ? LIMIT : abs(divisor);
	unsigned int result = 0;
	for (int i = 31; i >= 0; i--) {
		if ((t >> i) >= d) {//找出足够大的数2^n*divisor
			result += ((unsigned int)1) << i;//将结果加上2^n
			t -= d << i;//将被除数减去2^n*divisor
		}
	}
	if (result == LIMIT) {//特殊数不能将unsigned int转为int
		return INT_MIN;
	}
	else {
		return negative ? -(int)result : (int)result;//符号相异取反
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值