数学(四) -- LC[29][166] 两数相除与分数到小数

news2024/10/5 14:25:40

1 分数到小数

1.1 题目描述

        题目链接:https://leetcode.cn/problems/fraction-to-recurring-decimal/description/

1.2 思路分析

1. 长除法

        题目要求根据给定的分子和分母,将分数转成整数或小数。由于给定的分子和分母的取值范围都是 [ − 2 31 , 2 31 − 1 ] [-2^{31}, 2^{31}-1] [231,2311],为了防止计算过程中产生溢出,需要将分子和分母转成 64 位整数表示。

        将分数转成整数或小数,做法是计算分子和分母相除的结果。可能的结果有三种:整数、有限小数、无限循环小数。

        如果分子可以被分母整除,则结果是整数,将分子除以分母的商以字符串的形式返回即可。

        如果分子不能被分母整除,则结果是有限小数或无限循环小数,需要通过模拟长除法的方式计算结果。为了方便处理,首先根据分子和分母的正负决定结果的正负(注意此时分子和分母都不为 0),然后将分子和分母都转成正数,再计算长除法。

        计算长除法时,首先计算结果的整数部分,将以下部分依次拼接到结果中:

  1. 如果结果是负数则将负号拼接到结果中,如果结果是正数则跳过这一步;
  2. 将整数部分拼接到结果中;
  3. 将小数点拼接到结果中。

        完成上述拼接之后,根据余数计算小数部分。

        计算小数部分时,每次将余数乘以 10,然后计算小数的下一位数字,并得到新的余数。重复上述操作直到余数变成 0 或者找到循环节。

  • 如果余数变成 0,则结果是有限小数,将小数部分拼接到结果中。
  • 如果找到循环节,则找到循环节的开始位置和结束位置并加上括号,然后将小数部分拼接到结果中。

        如何判断是否找到循环节?注意到对于相同的余数,计算得到的小数的下一位数字一定是相同的,因此如果计算过程中发现某一位的余数在之前已经出现过,则为找到循环节。为了记录每个余数是否已经出现过,需要使用哈希表存储每个余数在小数部分第一次出现的下标。

        假设在计算小数部分的第 i i i 位之前,余数为 remainder i \textit{remainder}_i remainderi,则在计算小数部分的第 i i i 位之后,余数为 remainder i + 1 \textit{remainder}_{i+1} remainderi+1

        假设存在下标 j j j k k k,满足 j ≤ k j \le k jk remainder j = remainder k + 1 \textit{remainder}_j = \textit{remainder}_{k+1} remainderj=remainderk+1,则小数部分的第 k + 1 k+1 k+1 位和小数部分的第 j j j 位相同,因此小数部分的第 j j j 位到第 k k k 位是一个循环节。在计算小数部分的第 k k k 位之后就会发现这个循环节的存在,因此在小数部分的第 j j j 位之前加上左括号,在小数部分的末尾(即第 k k k 位之后)加上右括号。

class Solution:
    def fractionToDecimal(self, numerator: int, denominator: int) -> str:
        # 如果本身能够整除,直接返回计算结果
        if numerator % denominator == 0: return str(numerator//denominator)
        res = []
        if numerator * denominator < 0:             # 如果其一为负数,先追加负号
            res.append('-')
        numerator, denominator = abs(numerator), abs(denominator)
        res.append(str(numerator//denominator))     #  计算整数部分,并将余数赋值给 remainder
        res.append('.')
        remainder = numerator % denominator
        index_map = dict()
        while remainder and remainder not in index_map:
            index_map[remainder] = len(res)         # 记录当前余数所在答案的位置,并继续模拟除法运算
            remainder *= 10
            res.append(str(remainder//denominator))
            remainder %= denominator
        if remainder:                   # 当前余数之前出现过,则将出现位置和最后位置添加'()'
            ind = index_map[remainder]
            res.insert(ind, '(')
            res.append(')')
        return ''.join(res)

复杂度分析

  • 时间复杂度: O ( l ) O(l) O(l),其中 l l l 是答案字符串的长度,这道题中 l ≤ 1 0 4 l \le 10^4 l104。对于答案字符串中的每一个字符,计算时间都是 O ( 1 ) O(1) O(1)
  • 空间复杂度: O ( l ) O(l) O(l),其中 l l l 是答案字符串的长度,这道题中 l ≤ 1 0 4 l \le 10^4 l104。空间复杂度主要取决于答案字符串和哈希表,哈希表中的每个键值对所对应的下标各不相同,因此键值对的数量不会超过 l l l

2 两数相除

2.1 题目描述

        题目链接:https://leetcode.cn/problems/divide-two-integers/description/

2.2 思路分析

1. 二分查找

        如果除法结果溢出,那么我们需要返回 2 31 − 1 2^{31}-1 2311 作为答案。因此在编码之前,我们可以首先对于溢出或者容易出错的边界情况进行讨论:

  • 当被除数为 32 位有符号整数的最小值 − 2 31 -2^{31} 231 时:
    • 如果除数为 1,那么我们可以直接返回答案 − 2 31 -2^{31} 231
    • 如果除数为 −1,那么答案为 2 31 2^{31} 231,产生了溢出。此时我们需要返回 2 31 − 1 2^{31} - 1 2311
  • 当除数为 32 位有符号整数的最小值 s − 2 31 s-2^{31} s231 时:
    • 如果被除数同样为 − 2 31 -2^{31} 231,那么我们可以直接返回答案 111;
    • 对于其余的情况,我们返回答案 0。
  • 当被除数为 0 时,我们可以直接返回答案 0。

        对于一般的情况,根据除数和被除数的符号,我们需要考虑 444 种不同的可能性。因此,为了方便编码,我们可以将被除数或者除数取相反数,使得它们符号相同。

        如果我们将被除数和除数都变为正数,那么可能会导致溢出。例如当被除数为 − 2 31 -2^{31} 231 时,它的相反数 2 31 2^{31} 231 产生了溢出。因此,我们可以考虑将被除数和除数都变为负数,这样就不会有溢出的问题,在编码时只需要考虑 1 种情况了。

        如果我们将被除数和除数的其中(恰好)一个变为了正数,那么在返回答案之前,我们需要对答案也取相反数。

方法一:二分查找

        根据「前言」部分的讨论,我们记被除数为 X,除数为 Y,并且 X 和 Y 都是负数。我们需要找出 X/Y 的结果 Z。Z 一定是正数或 0。

        根据除法以及余数的定义,我们可以将其改成乘法的等价形式,即:

Z × Y ≥ X ≥ ( Z + 1 ) × Y Z\times Y \geq X \geq (Z+1) \times Y Z×YX(Z+1)×Y

        因此,我们可以使用二分查找的方法得到 ZZZ,即找出最大的 ZZZ 使得 Z×Y≥XZ \times Y \geq XZ×Y≥X 成立。

        由于我们不能使用乘法运算符,因此我们需要使用「快速乘」算法得到 Z × Y Z \times Y Z×Y 的值。

        由于我们只能使用 32 位整数,因此二分查找中会有很多细节。

        首先,二分查找的下界为 1,上界为 2 31 − 1 2^{31} - 1 2311。唯一可能出现的答案为 2 31 2^{31} 231 的情况已经被我们在「前言」部分进行了特殊处理,因此答案的最大值为 2 31 − 1 2^{31} - 1 2311。如果二分查找失败,那么答案一定为 0。

        在实现「快速乘」时,我们需要使用加法运算,然而较大的 Z 也会导致加法运算溢出。例如我们要判断 A + B 是否小于 C 时(其中 A,B,C 均为负数),A + B 可能会产生溢出,因此我们必须将判断改为 A < C − B A < C - B A<CB 是否成立。由于任意两个负数的差一定在 [ − 2 31 + 1 , 2 31 − 1 ] [-2^{31} + 1, 2^{31} - 1] [231+1,2311] 范围内,这样就不会产生溢出。

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

复杂度分析

  • 时间复杂度: O ( log ⁡ 2 C ) O(\log^2 C) O(log2C),其中 C C C 表示 32 位整数的范围。二分查找的次数为 O ( log ⁡ C ) O(\log C) O(logC),其中的每一步我们都需要 O ( log ⁡ C ) O(\log C) O(logC) 使用「快速乘」算法判断 Z × Y ≥ X Z \times Y \geq X Z×YX 是否成立,因此总时间复杂度为 O ( log ⁡ 2 C ) O(\log^2 C) O(log2C)
  • 空间复杂度: O ( 1 ) O(1) O(1)

2. 减法试除
思路一
        首先需要考虑正负号,处理为分子分母全是正数, 其次在返回的时候要注意是否溢出,如果溢出要判断。

        核心是div函数怎么写?例如方法1中的div函数, 利用二进制搜索的思想就是, 每次利用加法,将当前的 divisor 乘以两倍,并同时用 multiple 记录下乘以了 2 的多少次方, multiple 的变化过程是1,2,4,8,16 。。。

        因为任何一个数都可以用二进制的方法得到,所以我们可以利用二进制的思想来代表乘数 multiple, 最终能够得到一个 divisor * multiple = dividend 的multiple。

        举例:算 63 / 8 63 / 8 63/8 过程为: 63 / 8 = ( 63 − 32 ) / 8 + 4 = ( 63 − 32 − 16 ) / 8 + 2 + 4 = ( 63 − 32 − 16 − 8 ) / 8 + 1 + 2 + 4 = 7 63 / 8 = (63-32) / 8 + 4 = (63-32-16) / 8 + 2 + 4 = (63-32-16-8) / 8 + 1+ 2 + 4 = 7 63/8=(6332)/8+4=(633216)/8+2+4=(6332168)/8+1+2+4=7 其中 ( 63 − 32 − 16 − 8 ) / 8 = 7 / 8 = 0 (63-32-16-8) / 8 = 7 / 8 = 0 (6332168)/8=7/8=0

# 方法1:递归
class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        MIN_INT, MAX_INT = -2147483648, 2147483647  # [−2**31, 2**31−1]
        flag = 1                                    # 存储正负号,并将分子分母转化为正数
        if dividend < 0: flag, dividend = -flag, -dividend
        if divisor < 0: flag, divisor  = -flag, -divisor 
        
        def div(dividend, divisor):                 # 例:1023 / 1 = 512 + 256 + 128 + 64 + 32 + 16 + 8 + 4 + 1
            if dividend < divisor:
                return 0
            cur = divisor
            multiple = 1
            while cur + cur < dividend:             # 用加法求出保证divisor * multiple <= dividend的最大multiple
                cur += cur                          # 即cur分别乘以1, 2, 4, 8, 16...2^n,即二进制搜索
                multiple += multiple
            return multiple + div(dividend - cur, divisor)
        res = div(dividend, divisor)

        res = res if flag > 0 else -res             # 恢复正负号
        
        if res < MIN_INT:                           # 根据是否溢出返回结果
            return MIN_INT
        elif MIN_INT <= res <= MAX_INT:
            return res
        else:
            return MAX_INT


# 方法2:迭代
class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        MIN_INT, MAX_INT = -2147483648, 2147483647  # [−2**31, 2**31−1]
        flag = 1                                    # 存储正负号,并将分子分母转化为正数
        if dividend < 0: flag, dividend = -flag, -dividend
        if divisor < 0: flag, divisor  = -flag, -divisor 
        
        res = 0
        while dividend >= divisor:                  # 例:1023 / 1 = 512 + 256 + 128 + 64 + 32 + 16 + 8 + 4 + 1
            cur = divisor
            multiple = 1
            while cur + cur < dividend:             # 用加法求出保证divisor * multiple <= dividend的最大multiple
                cur += cur                          # 即cur分别乘以1, 2, 4, 8, 16...2^n,即二进制搜索
                multiple += multiple
            dividend -= cur                         # 辗转相减法
            res += multiple
        
        res = res if flag > 0 else -res             # 恢复正负号
        
        if res < MIN_INT:                           # 根据是否溢出返回结果
            return MIN_INT
        elif MIN_INT <= res <= MAX_INT:
            return res
        else:
            return MAX_INT

思路二

        用 2 i 2^i 2i 去作为乘法基数, x ∗ 2 i = x < < i x * 2^i = x << i x2i=x<<i。 从 2 31 2^{31} 231 试到 2 0 2^0 20 直到被除数被减到比除数小, 每个能满足除出来的最大的 2 的幂都加入答案, 也可以理解为每次计算出答案的 32 位中的某一位

class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        if dividend == -2147483648 and divisor == -1:
            return 2147483647
        a, b, res = abs(dividend), abs(divisor), 0
        for i in range(31, -1, -1):
            # 2^i * b <= a 换句话说 a/b = 2^i + (a-2^i*b)/b
            if (b << i) <= a:
                res += 1 << i
                a -= b << i
        return res if (dividend > 0) == (divisor > 0) else -res

参考

  • 两数相除——官方题解:https://leetcode.cn/problems/divide-two-integers/solutions/1041939/liang-shu-xiang-chu-by-leetcode-solution-5hic/
  • 减法试除:https://leetcode.cn/problems/divide-two-integers/solutions/1042741/pythonjavajavascript-jian-fa-shi-chu-by-amrow/
  • 二进制搜索的思想:https://leetcode.cn/problems/divide-two-integers/solutions/458026/29-python3-li-yong-er-jin-zhi-sou-suo-de-si-xiang-/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/560143.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux环境变量提权

linux提权信息收集 Exploit Database - Exploits for Penetration Testers, Researchers, and Ethical Hackers Vulnerability & Exploit Database - Rapid7 NVD - Home CVE -CVE SecWiki GitHub linux系统内核漏洞提权 脏牛提权漏洞&#xff1a; 脏牛提权&#xf…

推荐5个免费好用的UI模板网站!

1、即时设计 即时设计资源广场是一个聚集了大量优秀设计作品和大厂设计系统超过3000个UI组件库的设计师灵感库。该广场每月更新上百个精品模板&#xff0c;且还将这些模板分门别类按不同类型素材进行分类&#xff0c;其丰富的设计资源包括移动设计、网页设计、插画、线框图、矢…

Qt--信号和槽

写在前面 信号与槽机制是Qt中最重要的特性之一&#xff0c;也是其与其他GUI框架的主要区别之一。信号与槽机制允许不同对象之间进行通信和交互&#xff0c;从而实现程序的模块化和可重用性。 在Qt中&#xff0c;信号是一种事件&#xff0c;它可以被任何对象接收并执行相应的操…

Zookeeper、Nacos、Dubbo、Kafka之间的关系

1.Zookeeper Zookeeper 是 Apache Hadoop 的子项目&#xff0c;是一个树型的目录服务&#xff0c;支持变更推送&#xff0c;适合作为 Dubbo 服务的注册中心&#xff0c;工业强度较高。 Zookeeper的功能主要是它的树形节点来实现的。当有数据变化的时候或者节点过期的时候&…

AGV/AMR控制器--科聪

AGV/AMR控制器--科聪 1 行业介绍1.1 控制器概念1.2 行业发展1.3 竞争格局 2 科聪控制器 MRC50002.1 介绍2.2 支持多种导航方式2.3 适配各种轮系底盘2.4 核心参数2.5 优势灵活的二次开发平台&#xff1a;机器人设计软件&#xff08;xRobotStudio&#xff09;完备的实施调试工具&…

干货|写好论文,从一篇优秀的开题报告开始

Hello&#xff0c;大家好&#xff01; 这里是壹脑云科研圈&#xff0c;我是喵君姐姐~ 在今天的推文里&#xff0c;要给大家分享的是从开题报告到论文写作&#xff0c;快来一起看看哦~ 开题报告旨在总结与研究课题有关的立论依据、总体规划和预期研究成果&#xff0c;便于潜在…

一分钟带你了解网络安全(如何自学)

一、关于网络安全职业 早些年&#xff0c;网络安全刚起步&#xff0c;作为一个网络安全从业人员&#xff0c;最苦恼的事情就是每当回到村里变成狗蛋儿的时候&#xff0c;七大姑八大姨&#xff0c;邻里乡亲&#xff0c;村子里的各种人都会来找你&#xff0c;狗蛋儿&#xff0c;你…

(06)---STM32的Systick定时器与ADC

目录 【1】Systick定时器 概念 工作原理 时钟基准 【2】HAL_Delay函数分析 【3】定时器 基本概念 定时器分类 定时器组成 1.计数器 2.自动重装寄存器 3.预分频器 定时器计数原理 实验 2.PWM 定义 参数 工作原理 应用 练习&#xff1a;通过PWM信号调节LED灯亮度 练…

Convolutional Neural Network 的 PyTorch 实现(二)使用TensorRT进行推理加速

本文章针对 Windows 10 系统 目录 TensorRT 环境安装与配置zlibwapi.dll 安装与配置TensorRT 实现 CUDA CuDNN的安装&#xff1a; 参考文章 TensorRT 环境安装与配置 下载链接 TensorRT 本文章针对 Windows10、CUDA10.2 的PC&#xff0c;选择相对应的安装包完成下载。 解压后在…

迪赛智慧数——柱状图(基本柱状图):全球自动化无人机智能支出预测

效果图 全球自动化无人机智能支出及预测分析&#xff0c;2022年机器人流程自动化支出10.4十亿美元&#xff0c;智能流程自动化支出13十亿美元&#xff0c;人工智能业务操作达10.8十亿美元&#xff0c;未来&#xff0c;这些数字将进一步增长&#xff0c;自动化无人机智能也将拥有…

二战京东测试岗失败,真的后悔了....

两天&#xff0c;我的一个朋友去大厂面试&#xff0c;跟我聊天时说&#xff1a;输的很彻底… 我问她&#xff1a;什么情况&#xff1f; 她说&#xff1a;很后悔这5年来一直都干的是功能测试… 相信许多测试人也跟我朋友一样&#xff0c;从事了软件测试很多年&#xff0c;却依然…

小魔驼3.0下探至9万元背后,是毫末智行的“高位再进化”

作者 | 曾响铃 文 | 响铃说 是60分到90分难&#xff0c;还是90分到95分难&#xff1f; 这个问题不难回答——较高基数上的小幅度上升&#xff0c;要比较低基数上的大幅度上升困难得多。 这个道理在很多领域都十分适用&#xff0c;那些前沿技术领域更是如此&#xff0c;越到…

时钟、SysTick定时器、PWM、ADC

目录 【1】STM32的时钟系统 1.时钟基本概念 时钟源&#xff1a; 2.G030时钟源 3.时钟树 4.STM32CubeMX时钟树配置 【2】Systick定时器 1. 概念&#xff1a; 工作原理 时钟基准 【3】HAL_Delay函数分析 【4】定时器 基本概念 定时器分类 定时器组成 1.计数器 2.自…

软件测试----测试管理方法论

1、缺陷 &#xff08;1&#xff09;缺陷的主要变现&#xff1a; 1&#xff09;需求要求的功能没有实现 2&#xff09;实现了需求没有要求的功能 3&#xff09;软件中出现了明确指明不应该出现的错误 4&#xff09;需求虽未明确说明&#xff0c;但是应该实现的功能没有实现 5&…

MySQL高级|最强王者篇

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

【WSN覆盖】基于樽海鞘算法的三维无线传感器网络覆盖优化 三维WSN覆盖优化【Matlab代码#27】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第5节&#xff1a;资源获取】1. 原始樽海鞘算法2. 三维覆盖模型3. 部分代码展示4. 仿真结果展示5. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章第5节&#xff1a;资源获取】 1. 原始樽海鞘算法 2. 三维覆盖模…

RHCE 第六次作业

1、判断当前磁盘剩余空间是否有20G&#xff0c;如果小于20G&#xff0c;则将报警邮件发送给管理员&#xff0c;每天检查一次磁盘剩余空间。 2、判断web服务是否运行&#xff08;1、查看进程的方式判断该程序是否运行&#xff0c;2、通过查看端口的方式判断该程序是否运行&…

【面试篇】SpringIoC、AOP、MVC面试实战

version&#xff1a;1.0 文章目录 SpringSpring基础 / IoC&#x1f64e;‍♂️面试官&#xff1a;举例Spring的模块&#xff1f;&#x1f64e;‍♂️面试官&#xff1a;Spring、SpringMVC、Spring Boot关系&#xff1f;&#x1f64e;‍♂️面试官&#xff1a;说说对SpringIoC的…

谈谈你对JavaSE中compare、compareTo的使用与比较

1. 前言 在java当中&#xff0c;若是要进行比较&#xff0c;大家可能第一时间想到&#xff0c; 或是 !&#xff0c;这种数学上的比较符>、接下来&#xff0c;我就分别介绍并演示这两种实现方法。 需要的朋友可以参考下&#xff0c;这将又会是干货满满的一期&#xff0c;全程…

【复习笔记】FreeRTOS(一)

FreeRTOS在校期间自学过一段时间&#xff0c;然而出来工作却用不上。 最近在搞东西需要用到RTOS&#xff0c;特意把笔记整理一下&#xff0c;算是复习了。笔记整理主要来源于正点原子的开发文档、视频教程。 文章目录 一、FreeRTOS简介二、FreeTROS移植三、测试例程四、实验效果…