首页 > 编程语言 >LeetCode题解:29.两数相除【Python题解超详细,位运算、二分查找法、递归法】,知识拓展:位运算

LeetCode题解:29.两数相除【Python题解超详细,位运算、二分查找法、递归法】,知识拓展:位运算

时间:2024-11-24 10:31:26浏览次数:5  
标签:运算 temp 题解 左移 除数 dividend 两数 divisor

题目描述

        给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8 ,-2.7335 将被截断至 -2 。返回被除数 dividend 除以除数 divisor 得到的  。

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

示例 1:

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

示例 2:

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

提示:

  • -231 <= dividend, divisor <= 231 - 1
  • divisor != 0

解答

class Solution(object):
    def divide(self, dividend, divisor):
        """
        :type dividend: int
        :type divisor: int
        :rtype: int
        """
        # # 思路一:位运算求商
        # # 定义截断范围,即最大值和最小值
        # INT_MAX=2**31-1
        # INT_MIN=-2**31
        # # 特殊情况dividend=0
        # if dividend==0:
        #     return 0
        # # 确定结果符号
        # sign = -1 if (dividend<0)^(divisor<0)  else 1
        # #将其转化为正数,以便于运算
        # dividend,divisor=abs(dividend),abs(divisor)
        # # 初始化结果
        # result=0
        # # 位运算求商
        # # 外层循环
        # while dividend >= divisor:
        #     temp_divisor, multiple = divisor, 1
        #     # 内层循环
        #     while dividend > (temp_divisor << 1):
        #         # 对两者进行位运算,不断左移
        #         temp_divisor <<= 1
        #         multiple <<=1
        #     dividend-=temp_divisor
        #     result+=multiple
        # # 结果附加符号
        # result=sign*result
        # # 返回结果
        # return max(INT_MIN,min(INT_MAX,result))

        # 思路二:二分查找法       
        # # 定义截断范围,即最大值和最小值
        INT_MAX=2**31-1
        INT_MIN=-2**31
        # 特殊情况dividend=0
        if dividend==0:
            return 0
        # 确定结果符号
        sign = -1 if (dividend<0)^(divisor<0)  else 1
        #将其转化为正数,以便于运算
        dividend,divisor=abs(dividend),abs(divisor)
        # 初始化结果
        result=0
        # 二分查找的左右边界
        left,right=0,dividend
        while left<=right:
            mid=(left+right)//2
            # 根据判定条件,不断移动边界
            if mid*divisor <= dividend:
                result=mid
                left=mid+1
            else:
                right=mid-1
        # 结果附加符号
        result=sign*result
        # 返回结果
        return max(INT_MIN,min(INT_MAX,result))


        # # 思路三:迭代法
        # # 定义截断范围,即最大值和最小值
        # INT_MAX=2**31-1
        # INT_MIN=-2**31
        # # 特殊情况dividend=0
        # if dividend==0:
        #     return 0
        # # 确定结果符号
        # sign = -1 if (dividend<0)^(divisor<0)  else 1
        # #将其转化为正数,以便于运算
        # dividend,divisor=abs(dividend),abs(divisor)
        # # 初始化结果
        # result=0
        # # 迭代
        # def divide_helper(dividend,divisor):
        #     if dividend < divisor:
        #         return 0 
        #     # 外层循环
        #     temp_divisor, multiple = divisor, 1
        #     # 内层循环
        #     while dividend > (temp_divisor << 1):
        #         # 对两者进行位运算,不断左移
        #         temp_divisor <<= 1
        #         multiple <<=1
        #     dividend-=temp_divisor
        #     return multiple+divide_helper(dividend,divisor)
        # result=divide_helper(dividend,divisor)
        # # 结果附加符号
        # result=sign*result
        # # 返回结果
        # return max(INT_MIN,min(INT_MAX,result))

        思路一,位运算求商:通过位运算模拟除法。每次在被除数大于等于除数时,倍增当前除数(通过左移操作实现乘 2),同时记录对应的倍数。内层循环结束后,从被除数中减去倍增的最大除数,并将倍数累积到结果中。最终根据符号计算结果,并确保结果在有效范围内。

        思路二,二分查找法:利用二分查找在 [0, abs(dividend)] 范围内寻找商。通过比较 mid * divisor 和 dividend 来调整二分的左右边界,逐步逼近最大合法的商值。每次更新结果 result 时,确保当前的 mid 不超过被除数。最终根据符号和范围调整返回结果。

        思路三,递归法:递归地通过倍增法计算商。定义一个辅助函数,在每次递归中,通过左移操作寻找当前最大的倍数,然后从被除数中减去当前倍增的除数,递归计算剩余部分的商。最终将所有的倍数累积起来得到结果,再根据符号调整并返回结果,确保在合法范围内。

知识拓展:位运算

        相信有同学在看到方法一中的 << 符号时,第一感觉就是编程中还会用到远小于号吗?哈哈,其实它并不是远小于号,而是一种特殊的运算符号,位运算符。位运算可能听起来很“低级”,但它快得飞起!无论是高效算法还是底层优化,它都有一席之地。别怕,今天用轻松的方式让你掌握位运算,用它吊打常规乘除法

        位运算是什么?

        位运算,就是直接对数字的 二进制位 进行操作。二进制大家都知道:1010 啦,1101 啦(都是机器语言的心头好)。

        你平时的数字:5-7 在计算机中其实是以二进制形式存储的:

  • 正数:直接翻译成二进制,比如 5 = 101
  • 负数:用 补码表示(别怕,后面用不到负数位的操作)。

        常见位运算符

        Python 里的位运算符有如下几个:

运算符名称含义示例
<<左移把所有二进制位左移 n 位,相当于乘以 2^n5 << 1 = 10101 -> 1010
>>右移把所有二进制位右移 n 位,相当于整除 。5 >> 1 = 2101 -> 10
&按位与每个位同时为 1,结果才是 1。5 & 3 = 1101 & 011 = 001
``按位或只要有一个位是 1,结果就是 1。
^按位异或相同为 0,不同为 1。5 ^ 3 = 6101 ^ 011 = 110
~按位取反把每个位的 0 和 1 反转(包括符号位)。~5 = -6

        重点掌握左移 (<<) 和右移 (>>)

        这两个操作是位运算的灵魂!

  1. 左移 (<<):

    • 每左移一位,数值翻倍,后面补零。
    • 类似你写数学作业时,在数后面补 0(比如 10 * 10 = 100)。
    • 示例
      x = 3  # 二进制是 11
      print(x << 1)  # 输出 6 (二进制变成 110)
      print(x << 2)  # 输出 12 (二进制变成 1100)
      
  2. 右移 (>>):

    • 每右移一位,数值整除 2,后面直接砍掉。
    • 类似你平时整数除法(只保留商,余数扔掉)。
    • 示例
      x = 8  # 二进制是 1000
      print(x >> 1)  # 输出 4 (二进制变成 100)
      print(x >> 2)  # 输出 2 (二进制变成 10)
      

        在代码中的应用:倍增法

        还记得代码中这段吗?

while dividend >= (temp_divisor << 1):
    temp_divisor <<= 1
    multiple <<= 1

        这就是用 左移操作 实现的 倍增法。每次将 temp_divisormultiple 左移一位,相当于把当前除数和倍数都乘以 2。

  • 为什么用左移?
    因为直接操作位比用乘法快,而且不引入浮点数,超级高效!

  • 数学对应关系:
    如果你原来写:

    temp_divisor *= 2
    multiple *= 2
    

    那么用位运算就是:

    temp_divisor <<= 1
    multiple <<= 1
    

        总结一下

  • 左移 (<<) = 乘以 2,右移 (>>) = 整除 2。
  • 位运算在 效率简洁性 上无敌,但一定要注意适用场景(整数和非负数更安全)。
  • 今天用它解除了一个“不能用乘法”的除法题,你是不是觉得位运算简直有点酷?

学会了 <<>>,你就掌握了算法题中最常用的位运算技巧!下次再见,一起让代码飞起来~

标签:运算,temp,题解,左移,除数,dividend,两数,divisor
From: https://blog.csdn.net/m0_74420622/article/details/143988348

相关文章

  • 【题解】CF2031
    Atag:签到题注意到定住一个值后,左边的值全都得改,右边的值也全都得改注意到,定住的越多,需要改的就越少所以开桶记一下哪个值最多就行Btag:诈骗诈骗签到题读完题容易产生naive结论:当且仅当错位的两个地方相邻可以修复,其余情况全部无法修复感觉真不了一点,于是找三个数ABC......
  • 读数据质量管理:数据可靠性与数据质量问题解决之道13数据沿袭
    1. 数据沿袭1.1. MyDoom的病毒1.2. 现在,许多团队甚至整个公司都在使用数据,这要求数据管理的方式要更便于合作,同时也更不容许发生错误1.3. 从采用dbt和ApacheAirflow等开源工具来实现数据转换和编排,到使用Snowflake和Databricks等云端数据仓库和数据湖1.4. 数据仪表板和......
  • ybtoj 高效进阶题解索引
    密码:sunxuhetai2009--------------------云落的分割线--------------------基础算法第1章-递推算法第2章-贪心算法第3章-二分算法第4章-深搜算法第5章-广搜算法已完结--------------------云落的分割线--------------------图论第1章-并查集第4章-强连......
  • 攻防世界 web(新手模式)题解
    1.view_source题目描述:X老师让小宁同学查看一个网页的源代码,但小宁同学发现鼠标右键好像不管用了。根据题目提示直接F12查看源代码,发现答案就在源代码里2.get_post题目描述:X老师告诉小宁同学HTTP通常使用两种请求方法,你知道是哪两种吗?根据提示,我们需要用GET方式提......
  • 题解:AT_abc381_c [ABC381C] 11/22 Substring
    显然这个“11/22Substring”是以那个“/”为中心对称的。鉴于一个这样的字符串只能有一个“/”,而题目又要求最长,所以确定了“/”就能确定一个满足要求的子串。那思路就很简单了,只有两步:找到所有的“/”两边同时寻找相应的子串。别的,除了判断一下越界之外,就不用管了。......
  • [题解](更新中)[test06]2024/11/23 模拟赛 / 2023牛客OI赛前集训营-提高组(第三场) A~C
    原题页面:https://ac.nowcoder.com/acm/contest/65194Statements&Solution:https://www.luogu.com.cn/problem/U507978\(80+80+50+24=234\)。A贪心+双指针。根据贪心思想,大值选大、小值选小。我们按绝对值从大到小给\(a\)排序,再按从小到大给\(b\)排序,取双指针\(l=1,r=m\)......
  • 题解 - Birds
    题目题目大意一条直线上有\(n\)棵树,第\(i\)棵树上有\(c_i\)​只鸟。在第\(i\)棵树下召唤一只鸟的魔力代价是\(cost_i\)​。每召唤一只鸟,魔力上限会增加\(B\)。每向前走一棵树,会增加\(X\)的魔力。一开始的魔力和魔力上限都是\(W\)。你只能向前移动。问最多能够召......
  • 题解:CF1970E1 Trails (Easy)
    基本思路设\(dp_{i,j}\)为第\(i\)天时在第\(j\)个小屋的方案数,\(r_j\)为第\(j\)个小屋共有多少条路连接(即\(s_j+l_j\))。易得转移方程为\[dp_{i,j}=\sum_{k=1}^{m}dp_{i-1,k}\cdot(r_j\cdotr_k-l_j\cdotl_k)\](因为至少走一条短路,所以减去全长路的情况)代码实现......
  • 题解:CF644B Processing Queries
    CF644BProcessingQueries基本思路模拟题。对于每个工作申请,队列有如下两种操作:首先,将\(\leq\)当前开始时间(即\(t_i\))的所有操作弹出。接下来有两种选择:当队列已满,直接输出-1。当队列未满,更新结束时间并入队,输出新结束时间。代码实现#include<bits/stdc++.h>......
  • 题解:UVA13185 DPA Numbers I
    UVA13185DPANumbersI基本思路对于每个\(n\),枚举\(n\)的因数,最后判断因数大小即可。直接枚举到\(n-1\)有点浪费,所以可以只枚举到\(\sqrt{n}\),加上因数与\(n\)除以此因数的商。注意:最后要减去\(n\),且\(n\)为完全平方数时要减去一个\(\sqrt{n}\)。代码实现#incl......