[29]x 的平方根和两数相除

*内容来自leetcode

1.x 的平方根

题目要求

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2
示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

思路

由于最后得到的结果是整数,所以就是不用得到准确的结果,那其实就很简单了。

让i从1开始遍历,当i*i大于x时,说明i-1即为需要的结果。

class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0 or x == 1:
            return x
        for i in range(0,x+1):
            if i*i > x:
                return i - 1

当然这种办法的效率也是十分的低下。考虑用二分查找~~

class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0 or x == 1:
            return x
        left, right = 0, x
        while left <= right:
            middle = (left + right) // 2
            print(middle)
            if middle * middle > x :
                if (middle-1) * (middle-1) <= x:
                    return middle-1
                else:
                    right = middle-1
            else:
                left = middle+1

性能有了有效提升。

官方提供了三种思路,其中一种即为二分查找,就略过了。

其余两种之一是袖珍计算器算法

虽然不让用sqrt(),但是可以用别的函数。这种解法就是基于这种思路来实现的,可以将求平方根转化为如下式

转化之后就可以用内置计算ex和log的函数来完成。

写着写着力扣炸了。。。

class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0:
            return 0
        ans = math.exp(0.5*math.log(x))
        return ans if ans * ans <= x else ans - 1

还有一种方法是牛顿迭代法

就是最优化里的牛顿法

class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0 :return 0
        #f(x) = x^2 - C
        C,x0 = float(x), float(x)
        #y0 = x0**2 - C
        #dy = 2*x0
        #f'(x) = 2x
        #y0 = 2x0(x0)+b
        #b = y0 - 2x0(x0)
        #g(x) = dy*x - (x0*x0+C)
        while 1:
            xi = 0.5 * (x0 + C/x0)
            if (x0 - xi) < 1e-7:
                break
            x0 = xi
        return int(x0)

2.两数相除

题目要求

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

整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8 ,-2.7335 将被截断至 -2 。

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

注意:假设我们的环境只能存储 32 位 有符号整数,其数值范围是 [−231,  231 − 1] 。本题中,如果商 严格大于 231 − 1 ,则返回 231 − 1 ;如果商 严格小于 -231 ,则返回 -231 。

示例 1:

输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = 3.33333.. ,向零截断后得到 3 。
示例 2:

输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = -2.33333.. ,向零截断后得到 -2 。

思路​​​​​​​

最基本的思路其实和上面的第一题求平方根类似,就是找到一个最大数与除数相乘小于被除数,从1开始遍历会超时,考虑还是二分查找

但是不一样的地方在于被除数和除数存在为负的情况,但是不论正负,算出的数是一样的,就是需要确定正负。

class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        if dividend == 0:
            return 0

        ab_dividend = dividend if dividend > 0 else -dividend
        ab_divisor = divisor if divisor > 0 else -divisor
        flag = 1
        if dividend < 0:
            flag = -flag
        if divisor < 0:
            flag = -flag
        if ab_dividend == ab_divisor:
            return 1*flag
        left, right = 0, ab_dividend + 1
        while left <= right:
            middle = (left + right) // 2
            
            print(middle)
            if middle * ab_divisor > ab_dividend:
                if (middle-1) * ab_divisor <= ab_dividend:
                    if (middle-1) * flag > 2 ** 31 - 1:
                        return 2 ** 31 - 1
                    elif (middle-1) * flag < -(2 ** 31):
                        return -(2 ** 31)
                    else:
                        return (middle - 1) * flag
                else:
                    right = middle - 1
            else:
                left = middle + 1

结果显示,这种办法性能不咋地。同时回归这道题的要求

要求 不使用 乘法、除法和取余运算。

所以还需要将用到的乘法和除法修改,头大

#来自力扣官方
class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        INT_MIN, INT_MAX = -2**31, 2**31 - 1

        # 考虑被除数为最小值的情况
        if dividend == INT_MIN:
            if divisor == 1:
                return INT_MIN
            if divisor == -1:
                return INT_MAX
        
        # 考虑除数为最小值的情况
        if divisor == INT_MIN:
            return 1 if dividend == INT_MIN else 0
        # 考虑被除数为 0 的情况
        if dividend == 0:
            return 0
        
        # 一般情况,使用二分查找
        # 将所有的正数取相反数,这样就只需要考虑一种情况
        rev = False
        if dividend > 0:
            dividend = -dividend
            rev = not rev
        if divisor > 0:
            divisor = -divisor
            rev = not rev

        # 快速乘
        def quickAdd(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:
                        return False
                    result += add
                if z != 1:
                    # 需要保证 add + add >= x
                    if add < x - add:
                        return False
                    add += add
                # 不能使用除法
                z >>= 1
            return True
        
        left, right, ans = 1, INT_MAX, 0
        while left <= right:
            # 注意溢出,并且不能使用除法
            mid = left + ((right - left) >> 1)
            check = quickAdd(divisor, mid, dividend)
            if check:
                ans = mid
                # 注意溢出
                if mid == INT_MAX:
                    break
                left = mid + 1
            else:
                right = mid - 1

        return -ans if rev else ans

还有一种类二分查找

可以设计出如下的算法:

我们首先不断地将 Y 乘以 2(通过加法运算实现),并将这些结果放入数组中,其中数组的第 iii 项就等于 Y×2i
 。这一过程直到 Y 的两倍严格小于 X 为止。

我们对数组进行逆序遍历。当遍历到第 i项时,如果其大于等于 X,我们就将答案增加 2^i
 ,并且将 X 中减去这一项的值。

本质上,上述的逆序遍历就模拟了二分查找的过程。

#来自力扣官方
class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        INT_MIN, INT_MAX = -2**31, 2**31 - 1

        # 考虑被除数为最小值的情况
        if dividend == INT_MIN:
            if divisor == 1:
                return INT_MIN
            if divisor == -1:
                return INT_MAX
        
        # 考虑除数为最小值的情况
        if divisor == INT_MIN:
            return 1 if dividend == INT_MIN else 0
        # 考虑被除数为 0 的情况
        if dividend == 0:
            return 0
        
        # 一般情况,使用类二分查找
        # 将所有的正数取相反数,这样就只需要考虑一种情况
        rev = False
        if dividend > 0:
            dividend = -dividend
            rev = not rev
        if divisor > 0:
            divisor = -divisor
            rev = not rev
        
        candidates = [divisor]
        # 注意溢出
        while candidates[-1] >= dividend - candidates[-1]:
            candidates.append(candidates[-1] + candidates[-1])
        
        ans = 0
        for i in range(len(candidates) - 1, -1, -1):
            if candidates[i] >= dividend:
                ans += (1 << i)
                dividend -= candidates[i]

        return -ans if rev else ans

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值