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

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

  • 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位的longlong _numerator = numerator;long _denominator = denominator;//如果分子为0,直接返回零if(_numerator == 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);//整数部分为0if(_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加上当前的数addadd += 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++):

//记录add翻倍过程中,比dividend小的数值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) {//dividend比add[i]更小,add里面的倍数能够加载商里面if (add[i] >= dividend) {//下标是i,对应的倍数是2^i,可以用左移i位来实现ans += (1 << i);//dividend减去对应的adddividend -= add[i];}
}

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

还有一种办法是尝试每一个n,看当前的n*divisor和dividend的关系,因为dividend和divisor都变成了负数,因此dividend < n*divisor才能满足条件,n继续增大去比较。这里的n*divisor就用上述的快速乘去实现——需要改动一点,就是最后返回dividend 和 n*divisor 大小关系。
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){//末位为1,要加上一部分if(n&1){//如果计算过程中发现ans更小,就说明n太大了,直接返回falseif(dividend - add > ans )   return false;ans += add;}//如果当前不是最后一位,还要循环几轮if(n!=1){//而下一轮要用到的add已经比dividend更小【里面包括的divisor的倍数更多】//直接终止,返回falseif(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; //当前的mid是dividend <= mid * dividor,先赋值给andif(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.rhkb.cn/news/121217.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uni-app 之 v-on:click点击事件

uni-app 之 v-on:click点击事件 image.png <template><!-- vue2的<template>里必须要有一个盒子&#xff0c;不能有两个&#xff0c;这里的盒子就是 view--><view>--- v-on:click点击事件 ---<view v-on:click"onclick">{{title}}<…

周赛361(模拟、枚举、记忆化搜索、统计子数组数目(前缀和+哈希)、LCA应用题)

文章目录 周赛361[2843. 统计对称整数的数目](https://leetcode.cn/problems/count-symmetric-integers/)模拟 [2844. 生成特殊数字的最少操作](https://leetcode.cn/problems/minimum-operations-to-make-a-special-number/)记忆化搜索枚举 [2845. 统计趣味子数组的数目](http…

港陆证券:五日线破位怎么看?

在股票交易中&#xff0c;五日线是个重要的技术指标之一&#xff0c;它能够反映出最近的商场趋势。假如五日线破位&#xff0c;这意味着商场呈现了趋势反转&#xff0c;出资者需求注重趋势改动&#xff0c;并采取相应的出资战略。 首先&#xff0c;咱们来看看五日线破位的原因…

2022年09月 C/C++(八级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C编程&#xff08;1~8级&#xff09;全部真题・点这里 第1题&#xff1a;道路 N个以 1 … N 标号的城市通过单向的道路相连:。每条道路包含两个参数&#xff1a;道路的长度和需要为该路付的通行费&#xff08;以金币的数目来表示&#xff09; Bob and Alice 过去住在城市 1.…

每日一题——旋转图像

旋转图像 题目链接 方法一&#xff1a;利用辅助数组 通过对示例的观察和分析&#xff0c;我们可以得到这样的结论&#xff1a; 对于原数组的下标为i行元素&#xff0c;顺时针旋转九十度后&#xff0c;都变成了下标为&#xff08;n-1-i&#xff09;列元素。如图所示&#xff…

es倒排索引深入解读

文章目录 一. Lucene二.倒排索引算法2.1 Posting List压缩算法2.1.1 FOR2.1.2 RoaringBitmap压缩 2.3 FST压缩算法2.3.1 trie前缀树原理2.3.2 FST构建过程NFADFAFSMFSAFST:有限状态转换机构建原理FST在lucene中实现原理 1.什么是搜索引擎? 全文搜索引擎: 自然语言处理(NLP)、爬…

关于git约定式提交IDEA

背景 因为git提交的消息不规范导致被乱喷&#xff0c;所以领导统一规定了约定式提交 官话 约定式提交官网地址 约定式提交规范是一种基于提交信息的轻量级约定。 它提供了一组简单规则来创建清晰的提交历史&#xff1b; 这更有利于编写自动化工具。 通过在提交信息中描述功能…

docker使用(一)生成,启动,更新(容器暂停,删除,再生成)

docker使用&#xff08;一&#xff09; 编写一个 Dockerfile构建镜像构建失败构建成功 运行镜像运行成功 修改代码后再次构建请不要直接进行构建&#xff0c;要将原有的旧容器删除或暂停停止成功删除成功再次构建且构建成功&#xff01; 要创建一个镜像&#xff0c;你可以按照以…

stable diffusion实践操作-hypernetworks

系列文章目录 本文专门开一节写hypernetworks的内容&#xff0c;在看之前&#xff0c;可以同步关注&#xff1a; stable diffusion实践操作 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 系列文章目录前言一、h…

CSS中如何实现元素的旋转和缩放效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 元素的旋转和缩放效果⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏…

【Unity3D】UI Toolkit元素

1 前言 UI Toolkit简介 中介绍了 UI Builder、样式属性、UQuery、Debugger&#xff0c;UI Toolkit容器 中介绍了 VisualElement、ScrollView、ListView、GroupBox 等容器&#xff0c;UI Toolkit样式选择器 中介绍了简单选择器、复杂选择器、伪类选择器等样式选择器&#xff0c;…

韩老师java教程

基础知识 进制 进制首位表示方式二进制0B十进制无八进制0十六进制0X 进制转换 x进制转十进制 正常&#xff0c;没什么问题 十进制转x进制 将该数不断除以x&#xff0c;直到商为0为止&#xff0c;然后将每一步得到的余数倒过来&#xff0c;就是对应的x进制 二进制转八进…

酷开科技丨酷开系统,把电影院给你搬回家

在繁忙、混乱的快节奏工作中&#xff0c;人们总是渴望在下班后&#xff0c;逃离工作的桎梏找到一丝慰藉&#xff0c;看电影&#xff0c;则成为了很多人宣泄情感、放松心情的一种方式。但是&#xff0c;电影院的时间和地点总是那么不受控制&#xff0c;要么地点太远、要么场次不…

OS | 第5章 插叙:进程API

OS | 第5章 插叙&#xff1a;进程API 文章目录 OS | 第5章 插叙&#xff1a;进程API5.1 fork()系统调用代码过程分析 5.2 wait()系统调用5.3 exec() 系统调用执行过程 为什么这样设计API&#xff1f;shell执行过程shell 重定向pipe()>>>>> 欢迎关注公众号【三戒…

IDEA报错:Plugin ‘org.springframework.boot:spring-boot-maven-plugin:‘ not found

问题&#xff1a; 使用IDEA新建spring boot项目&#xff0c;报错如下&#xff1a; Plugin org.springframework.boot:spring-boot-maven-plugin: not found解决办法&#xff1a; 1.在本地maven仓库中找到spring-boot-maven-plugin的版本号 2.在pom.xml文件中添加对应的版本…

城市小车的优势,用五菱宏光mini,轻松应对城市拥堵与环保挑战。

掌握五菱宏光mini的驾驶技巧&#xff0c;让拥堵不再困扰你 合理利用车辆尺寸&#xff0c;轻松穿梭于城市道路 五菱宏光mini的尺寸小巧&#xff0c;长度不到3米&#xff0c;宽度不到1.5米&#xff0c;让你可以在狭窄的城市街道上轻松穿梭。掌握这一技巧&#xff0c;让你在拥堵…

什么是瓷片电容封装 | 百能云芯

瓷片电容封装是一种常见的电子元件封装方式&#xff0c;它广泛应用在电子设备中&#xff0c;用于存储和释放电荷&#xff0c;以实现电路的稳定工作。在本文中&#xff0c;我们将详细介绍瓷片电容封装的特点以及用途。 瓷片电容封装的特点&#xff1a; 瓷片电容是一种以陶瓷材料…

【USRP】调制解调系列6:16APSK、32APSK 、基于labview的实现

APSK APSK是&#xff0c;与传统方型星座QAM&#xff08;如16QAM、64QAM&#xff09;相比&#xff0c;其分布呈中心向外沿半径发散&#xff0c;所以又名星型QAM。与QAM相比&#xff0c;APSK便于实现变速率调制&#xff0c;因而很适合目前根据信道及业务需要分级传输的情况。当然…

机器学习前沿:改进自身缺陷,满足新战略

前机械师&#xff08; 来源) 一、说明 机器学习在人工智能历史上扮演重要角色&#xff0c;然而&#xff0c;存在问题也不少。为了适应新时代和新任务&#xff0c;不做出重大改进是不可能的&#xff0c;本篇就一些突出问题和改进做出讨论。以便读者掌握未来的思路和方向。 二、机…

1783_CMD启动MATLAB同时执行一个脚本

全部学习汇总&#xff1a; GitHub - GreyZhang/g_matlab: MATLAB once used to be my daily tool. After many years when I go back and read my old learning notes I felt maybe I still need it in the future. So, start this repo to keep some of my old learning notes…