【leetcode 力扣刷题】数学题之除法:哈希表解决商的循环节➕快速乘求解商

news2024/11/20 18:27:27

两道和除法相关的力扣题目

  • 166. 分数到小数
  • 29. 两数相除
    • 快速乘
    • 解法一:快速乘变种
    • 解法二: 二分查找 + 快速乘

166. 分数到小数

题目链接:166. 分数到小数
题目内容:
在这里插入图片描述
题目是要求我们把一个分数变成一个小数,并以字符串的形式返回。按道理,直接分子numerator除以分母denominator就得到了小数,转换成string返回就好。题目要求里指出了特殊情况——小数部分如果有循环,就把啊循环节括在括号里

那么问题来了,怎么知道有没有循环呢。循环节就是一段循环出现的数字,我们即便是存下来小数部分的每一位数,也不能说有数字重复了就是循环节的开始,比如0.121113,我们不能在1第二次出现的时候就判断上一个1到这个1之前就是循环节。那么是以什么重复出现判断是有循环节呢? A➗B得到商为C,余数为R,计算小数部分时每后移一位将当前余数补0进行运算, 最终余数R==0,表示能够整除,直接将结果转换成string即可;对于有循环节的小数部分,出现重复余数时,表示有循环节。因为从该余数开始计算,一定会再变成这个余数,这样循环下去。那么这个余数第一次计算出的小数,到第二次出现这个余数对应的那位小数,之间的小数计算循环节。 在这里插入图片描述
判断是否一个余数是否出现过,用hash表记录。由于同时要记录余数,以及余数出现的位置【以便确定循环节开始和结束位置】,因此用map。代码如下(C++):

class Solution {
public:
    string fractionToDecimal(int numerator, int denominator) {
    	//防止溢出,32位int转换成64位的long
        long _numerator = numerator;
        long _denominator = denominator;
        //如果分子为0,直接返回零
        if(_numerator == 0) //分子为0直接返回
            return "0";
        //能够整除,直接返回
        if(_numerator % _denominator == 0) 
            return to_string(_numerator/_denominator);
		//不能整除的情况,ans表示结果字符串
        string ans;
        //异号先添加负号
        if(_numerator>0&&_denominator<0 || _numerator<0&&_denominator>0){
            ans.push_back('-');
        }
        //都变成绝对值,计算结果数值部分
        _numerator = abs(_numerator);
        _denominator = abs(_denominator);
		//整数部分为0
        if(_numerator < _denominator)
        	ans.push_back('0');
        //计算整数部分
        else{ 
            ans = ans + to_string(_numerator/_denominator);
            //numerator变成余数
            _numerator = _numerator % _denominator;
        }
		//添加小数点
        ans.push_back('.');     
        //map记录余数以及出现的位置
        unordered_map <long,int> remainder;
        int idx = ans.size();
        remainder.insert({_numerator,idx});
        while(_numerator){
            _numerator *= 10;
            ans = ans + to_string(_numerator/_denominator);
            _numerator = _numerator % _denominator; 
            //如果余数重复出现就break,有循环 
            if(remainder.count(_numerator))
                break;
            remainder.insert({_numerator,++idx});                
        }
        //如果余数不为0,就有循环,循环节部分添加()
        if(_numerator) {
            ans.insert(remainder[_numerator],"(");
            ans += ')';
        }
        return ans; 
    }
};

29. 两数相除

题目链接:29. 两数相除
题目内容:
在这里插入图片描述
理解题意就是做整数除法,返回结果截取小数部分。比如-7/3 = -2,9/4 =2这样。问题在于两个地方:1、给的dividend和divisor,可能结果会溢出;2、不能使用乘法、除法和求余,但是要完成除法求得商。
对于第一个问题,单独考虑一些情况:

  • 只有在dividend = -2^31,且divisor = -1的时候,结果为2^31,会溢出;
  • 当dividend = 0 时,直接返回0;
  • 当divisor = 1 时,直接返回dividend;
    对于第二个问题,乘法的本质是加法,可以用快速乘这个方法,用加法来完成乘法操作。除法的本质是减法,而加减是一样的,因此也能用快速乘来完成。 因此本题的重点是快速乘。
    另外还需要注意dividend和divisor的符号问题,两个数有四种符号组合,同为正、同为负、一正一负、一负一正,只有在异号的情况下,结果才为负。 确定结果负号后,数值部分计算,就可以将两个数变成都是正的,但此题如果dividend 或者 divisor是-2^31,变成正数就溢出了,因此统一变成负数

快速乘

快速乘,即用加法来实现乘法,但是不是一个一个加,而是将数字每次翻倍,成倍成倍的加。7*5可以看成是7+7+7+7+7,5的二进制表示是(101),7+7+7+7+7组合一下,等于1*[(7+7)+(7+7)] + 0*(7+7) + 1*7,即第一次是+7,之后是+(7+7),再下一轮是+[(7+7)+(7+7)],每一轮要加的是上一轮的2倍,这个两倍直接用add = add + add来实现,也补需要乘法。每一轮还需要乘以倍数二进制表达式对应位的数值0或者1。代码模板如下(C++):

int quick_mul(int x, int n){
	int ans = 0; 
	int add = x;
	while(n){
		if(n&1) //末位为1
		//【实际上由于n的右移操作,这一步是在看对应位是否为1
		//比如5 = 101,第一次循环101,末位为1,加上7
		//第二次10,末位为0,应该加7+7,实际加0
		//第三次1,末位为1,加上(7+7)+(7+7)】
			ans += add;  //结果ans加上当前的数add
		add += add; //add翻倍
		n >>= 1; //n右移一位
	}
	return ans;
}

解法一:快速乘变种

快速乘是在知道了一个数要乘以几之后快速求解答案的过程,除法是要去求被除数是除数的几倍,因此不能直接使用快速乘,但是可以结点快速乘中数字加倍的思想,快速找到商。 判断dividend里面还能不能有一个完整的divisor,是需要|dividend| >= |divisor|,因为都变成了负数,即dividend <= divisor就证明dividend里面有至少一个divisor,商还能加上一部分。那么dividend里面有多少个divisor需要去试,先试有1个【add = divisor】,然后试有2个【add = add + add】,然后试有4个【继续add = add + add】,然后试有8个……这样循环下去,直到某个数使得dividend > add + add了,就说明add + add里面倍数太大了,应该是add里面的倍数。之后dividend减去当前add,剩下的继续去找里面有一个divisor。
这里需要注意判断dividend < add + add,可能有溢出,当add 在-2^31 ~ -2^30之间,add+add就小于-2^31,就溢出了,所以应该改成dividend - add < add。
完整代码如下(C++):

class Solution {
public:
    int divide(int dividend, int divisor) {
        //特殊情况
        if(dividend == 0) return 0;
        if(divisor == 1) return dividend;
        //可能溢出的情况
        if(dividend == INT_MIN && divisor == -1) return INT_MAX;
        if(divisor == INT_MIN){
            return dividend == INT_MIN ? 1:0;
        }
        //防止溢出,正数变复数
        int rev = 1;
        if(dividend > 0){
            dividend = -dividend;
            rev = -rev;
        }
        if(divisor > 0){
            divisor = -divisor;
            rev = -rev;
        }

        int ans = 0;   
        //被除数和除数都为负数,被除数小于等于除数商才大于0    
        while(dividend <= divisor){
            int add = divisor;
            int result = 1;
            while(dividend - add <= add){
                result <<= 1; //当前商翻倍
                add <<= 1;    //翻倍
            }  
            ans += result;  //加上部分结果
            dividend -= add;  //剩余部分继续求商       
        }
        return ans*rev; //乘以符号翻转
    }
};

这里在dividend更新更dividend - add后,add又变成divisor从1倍开始尝试这其实是多余的,可以将add+=add整翻倍过程的数值记录起来,优化代码(C++):

 while (add.back() >= dividend - add.back()) {
            add.push_back(add.back() + add.back());
        }
        int ans = 0;
        for (int i = add.size() - 1; i >= 0; --i) {
            if (add[i] >= dividend) {
                ans += (1 << i);
                dividend -= add[i];
            }
        }

解法二: 二分查找 + 快速乘

还有一种办法是尝试每一个n,看当前的ndivisor和dividend的关系,因为dividend和divisor都变成了负数,因此dividend < ndivisor才能满足条件,n继续增大去比较。这里的ndivisor就用上述的快速乘去实现——需要改动一点,就是最后返回dividend 和 ndivisor 大小关系。
n是0到2^31-1之间的一个数,要找到合适的n,可以用二分法,相较于依次挨个比较,时间复杂度更优,代码如下(C++):

class Solution {
public:
    bool quick_mul(int divisor, int n, int dividend){
        int ans = 0;
        int add = divisor;
        while(n){
            if(n&1){
                if(dividend - add > ans )   
                    return false;
                ans += add;
            }
            if(n!=1){
                if(dividend - add > add)
                    return false;
                add += add;
            }
            n >>= 1;
        }
        return true;
    }

    int divide(int dividend, int divisor) {
        //特殊情况
        if(dividend == 0) return 0;
        if(divisor == 1) return dividend;
        //可能溢出的情况
        if(dividend == INT_MIN && divisor == -1) return INT_MAX;
        if(divisor == INT_MIN){
            return dividend == INT_MIN ? 1:0;
        }
        //防止溢出,正数变复数
        int rev = 1;
        if(dividend > 0){
            dividend = -dividend;
            rev = -rev;
        }
        if(divisor > 0){
            divisor = -divisor;
            rev = -rev;
        }

        int ans = 0, left = 1, right = INT_MAX;
        //二分查找的过程
        while(left <= right){
            int mid = left + ((right - left)>>1);
            if (quick_mul(divisor, mid, dividend)){
                ans = mid;
                if(mid == INT_MAX)
                    break;
                left = mid + 1;
            }
            else{

                right = mid - 1;
            }
        } 
        return ans*rev; //乘以符号翻转
    }
};

二分查找过程是官方题解,但是我不太理解为什么一定要and = mid这一步赋值,直接返回mid是不对的……
解释:在-7/3这个情况下,结果是-2;left一直为1,right一直减小到3,此时mid = 2,对应quick_mul返回true,left = 3;此时仍然满足循环条件,mid 更新为 3 ,但是此时quick_mul返回的是false。也就是一个quick_mul返回true的mid可能是答案,需要记录,之后如果还有mid满足条件就更新。但是满足条件后mid可能还会更新,但更新后可能就不满足条件了。因此直接返回mul是不正确的。

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

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

相关文章

go锁-waitgroup

如果被等待的协程没了&#xff0c;直接返回 否则&#xff0c;waiter加一&#xff0c;陷入sema add counter 被等待协程没做完&#xff0c;或者没人在等待&#xff0c;返回 被等待协程都做完&#xff0c;且有人在等待&#xff0c;唤醒所有sema中的协程 WaitGroup实现了一组协程…

【MySQL】基础语法总结

MySQL 基础语句 一、DDL 数据库定义语言 1.1CREATE 创建 1.1.1 创建数据库 语法结构 CREATE DATABASE database_name;示例 CREATE DATABASE demo;1.1.2 创建表 语法结构 CREATE TABLE 表名 (列1 数据类型,列2 数据类型,... );示例 CREATE TABLE new_user (id INT PRIMARY KE…

python爬虫数据解析xpath

一、环境配置 1、安装xpath 下载地址&#xff1a;百度网盘 请输入提取码 第一步&#xff1a; 下载好文件后会得到一个没有扩展名的文件&#xff0c;重命名该文件将其改为.rar或者.zip等压缩文件&#xff0c;解压之后会得到一个.crx文件和一个.pem文件。新建一个文件夹&…

AI工人操作行为流程规范识别算法

AI工人操作行为流程规范识别算法通过yolov7python网络模型框架&#xff0c;AI工人操作行为流程规范识别算法对作业人员的操作行为进行实时分析&#xff0c;根据设定算法规则判断操作行为是否符合作业标准规定的SOP流程。Yolo意思是You Only Look Once&#xff0c;它并没有真正的…

怎样免费在公司访问家中的树莓派

最近拿起了大学时买的树莓派&#xff0c;刚好看到了一篇文章写到无公网IP&#xff0c;从公网SSH远程访问家中的树莓派 便来试试&#xff1a; 我的树莓派之前装过ssh&#xff0c;所以插上电就能用了。其实过程很简单&#xff0c;只需要在树莓派中下载一个cpolar即可。 curl -…

CSS3常用的新功能总结

CSS3常用的新功能包括圆角、阴渐变、2D变换、3D旋转、动画、viewpor和媒体查询。 圆角、阴影 border-redius 对一个元素实现圆角效果&#xff0c;是通过border-redius完成的。属性为两种方式&#xff1a; 一个属性值&#xff0c;表示设置所有四个角的半径为相同值&#xff…

UE5 实现Niagara粒子特效拖尾效果

文章目录 前言实现效果闪现示例疾跑示例实现新建Niagara系统应用Niagara系统实现拖尾效果应用拖尾颜色前言 本文采用虚幻5.2.1版本,对角色粒子特效拖尾效果进行讲解,从零开始,来实现此效果。此效果可以在角色使用某一技能时触发,比如使用闪现、疾跑等等。 实现效果 闪现示…

深入剖析 Golang 程序启动原理 - 从 ELF 入口点到GMP初始化到执行 main!

大家好&#xff0c;我是飞哥&#xff01; 在过去的开发工作中&#xff0c;大家都是通过创建进程或者线程来工作的。Linux进程是如何创建出来的&#xff1f; 、聊聊Linux中线程和进程的联系与区别&#xff01; 和你的新进程是如何被内核调度执行到的&#xff1f; 这几篇文章就是…

每日一题(链表中倒数第k个节点)

每日一题&#xff08;链表中倒数第k个节点&#xff09; 链表中倒数第k个结点_牛客网 (nowcoder.com) 思路: 如下图所示&#xff1a;此题仍然定义两个指针&#xff0c;fast指针和slow指针&#xff0c;假设链表的长度是5&#xff0c;k是3&#xff0c;那么倒数第3个节点就是值为…

解决WebSocket通信:前端拿不到最后一条数据的问题

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

最新智能AI系统ChatGPT网站程序源码+详细图文搭建教程/支持GPT4/WEB-H5端+微信公众号版源码

一、AI系统 如何搭建部署AI创作ChatGPT系统呢&#xff1f;小编这里写一个详细图文教程吧&#xff01;SparkAi使用Nestjs和Vue3框架技术&#xff0c;持续集成AI能力到AIGC系统&#xff01; 1.1 程序核心功能 程序已支持ChatGPT3.5/GPT-4提问、AI绘画、Midjourney绘画&#xf…

MySQL高阶语句(三)

一、NULL值 在 SQL 语句使用过程中&#xff0c;经常会碰到 NULL 这几个字符。通常使用 NULL 来表示缺失 的值&#xff0c;也就是在表中该字段是没有值的。如果在创建表时&#xff0c;限制某些字段不为空&#xff0c;则可以使用 NOT NULL 关键字&#xff0c;不使用则默认可以为空…

Vue中过滤器如何使用?

过滤器是对即将显示的数据做进⼀步的筛选处理&#xff0c;然后进⾏显示&#xff0c;值得注意的是过滤器并没有改变原来 的数据&#xff0c;只是在原数据的基础上产⽣新的数据。过滤器分全局过滤器和本地过滤器&#xff08;局部过滤器&#xff09;。 目录 全局过滤器 本地过滤器…

Python之父加入微软三年后,Python嵌入Excel!

近日&#xff0c;微软传发布消息&#xff0c;Python被嵌入Excel&#xff0c;从此Excel里可以平民化地进行机器学习了。只要直接在单元格里输入“PY”&#xff0c;回车&#xff0c;调出Python&#xff0c;马上可以轻松实现数据清理、预测分析、可视化等等等等任务&#xff0c;甚…

好马配好鞍:Linux Kernel 4.12 正式发布

Linus Torvalds 在内核邮件列表上宣布释出 Linux 4.12&#xff0c;Linux 4.12 的主要特性包括&#xff1a; BFQ 和 Kyber block I/O 调度器&#xff0c;livepatch 改用混合一致性模型&#xff0c;信任的执行环境框架&#xff0c;epoll 加入 busy poll 支持等等&#xff0c;其它…

从零开始,探索C语言中的字符串

字符串 1. 前言2. 预备知识2.1 字符2.2 字符数组 3. 什么是字符串4. \04.1 \0是什么4.2 \0的作用4.2.1 打印字符串4.2.2 求字符串长度 1. 前言 大家好&#xff0c;我是努力学习游泳的鱼。你已经学会了如何使用变量和常量&#xff0c;也知道了字符的概念。但是你可能还不了解由…

2023_Spark_实验四:SCALA基础

一、在IDEA中执行以下语句 或者用windows徽标R 输入cmd 进入命令提示符 输入scala直接进入编写界面 1、Scala的常用数据类型 注意&#xff1a;在Scala中&#xff0c;任何数据都是对象。例如&#xff1a; scala> 1 res0: Int 1scala> 1.toString res1: String 1scala…

11 模型选择 + 过拟合和欠拟合

训练集&#xff1a;用于训练权重参数 验证集&#xff1a;用来调参&#xff0c;评价模型的好坏&#xff0c;选择合适的超参数 测试集&#xff1a;只用一次&#xff0c;检验泛化性能&#xff0c;实际场景下的数据 非大数据集通常使用K-折交叉验证 K-折交叉验证 一个数据集分成…

云原生Kubernetes:二进制部署K8S多Master架构(三)

目录 一、理论 1.K8S多Master架构 2.配置master02 3.master02 节点部署 4.负载均衡部署 二、实验 1.环境 2.配置master02 3.master02 节点部署 4.负载均衡部署 三、总结 一、理论 1.K8S多Master架构 (1) 架构 2.配置master02 &#xff08;1&#xff09;环境 关闭防…

Docker:自定义镜像

&#xff08;总结自b站黑马程序员课程&#xff09; 环环相扣&#xff0c;跳过部分章节和知识点是不可取的。 一、镜像结构 镜像是分层结构&#xff0c;每一层称为一个Layer。 ①BaseImage层&#xff1a;包含基本的系统函数库、环境变量、文件系统。 ②Entrypoint&#xff1…