Java实现两数相除

news2024/10/11 0:30:55

题意

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

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

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

难度

中等

示例

输入: dividend = 10, divisor = 3

输出: 3

解释: 10/3 = 3.33333.. ,向零截断后得到 3 。

输入: dividend = 7, divisor = -3

输出: -2

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

分析 1

题目要求不能用除法(/),也不能用乘法(*),还有 “取余(%)”,如果我们偏要用除法会怎么样呢?

class Solution {

public int divide(int dividend, int divisor) {

// 直接使用除法计算结果

long result = (long) dividend / divisor;



// 处理溢出情况

if (result > Integer.MAX_VALUE) {

return Integer.MAX_VALUE;

}

if (result < Integer.MIN_VALUE) {

return Integer.MIN_VALUE;

}



return (int) result;

}

}

来看一下运行结果:

其实有时候我们真解不出来的话,多多少少先把答案写上去,运行通过,但遇到大数就不会成功。

分析 2

题目要求不能用除法,不过我们处理溢出情况的写法,后面还是要用到的,尤其是 (long) dividend 这个强转的处理,其实就考察了基本数据类型的转换问题,包括最后返回的 (int) result,都是很细节的问题,但却很重要。

那既然不能用除法,我们应该怎么去计算两数相除呢?

用减法

除法基本上是减法的逆运算,它表示将一个数(被除数)分成几个相等的部分(除数)时,可以得到多少个这样的部分(商)。如果用减法来实现除法,本质上是在问:“我可以从被除数中减去多少次除数?”每减去一次,就相当于得到了一个单位的商。

假设我们要计算 dividend / divisor = quotient(被除数 / 除数 = 商),其中 dividend 和 divisor 是已知的,我们要找到 quotient。

①、初始化计数器:设置一个计数器 count,初始值为 0。这个计数器将用来记录我们能从被除数中减去多少次除数。

②、循环减除数:只要被除数 dividend 大于或等于除数 divisor,就从被除数中减去除数,并且计数器 count 加一。

while (dividend >= divisor) {
    dividend -= divisor;
    count++;
}

③、处理剩余部分:当被除数小于除数时,循环结束。此时,count 的值就是整数除法的结果(商),而此时的 dividend 是除法的余数。

假设我们要计算 10 / 3:

  1. 初始时,dividend = 10,divisor = 3,count = 0。
  2. 10 >= 3,执行减法 10 - 3 = 7,count = 1。
  3. 7 >= 3,再次执行减法 7 - 3 = 4,count = 2。
  4. 4 >= 3,再次执行减法 4 - 3 = 1,count = 3。
  5. 此时,1 < 3,循环结束,count = 3 是商,1 是余数。

我们来试一下,直接用 ACM 的模式在 Intellij IDEA 中打印结果:

public class TwoDivide {

    public static void main(String[] args) {

//        int divide = divide1(10, 3);
//        System.out.println(divide);

        System.out.println(divide1(10, 3));
        System.out.println(divide1(7, -3));
        System.out.println(divide1(0, 1));
        System.out.println(divide1(1, 1));
        System.out.println(divide1(1, 0));
        System.out.println(divide1(-2147483648, -1));
    }


    public static int  divide1( int dividend , int divisor){
        // 处理特殊情况
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE;
        }

        // 符号处理
        boolean negative = (dividend < 0) ^ (divisor < 0);
        long ldividend = Math.abs((long)dividend);
        long ldivisor = Math.abs((long)divisor);

        long result = 0;
        while (ldividend >= ldivisor) {
            ldividend -= ldivisor;
            result++;
        }

        return negative ? (int)-result : (int)result;
    }




很遗憾,前 5 个测试用例都是可以顺利通过的,但最后 1 个测试用例耗时会特别长,因为要计算 2147483648 / 1次,这个数太大了,所以我们要想办法优化一下。

优化减法实现除法的关键在于减少需要执行的减法操作次数,对吧?

我们可以结合减法和倍增思想,每次减去 divisor 的倍数,这样就可以减少减法的次数。

  1. 初始时,令一个临时除数等于除数,每次尝试从被除数中减去临时除数。
  2. 如果可以减去,更新被除数(减去临时除数),临时除数倍增(乘以 2),同时记录下减去的次数。
  3. 如果不可以减去,则临时除数回退到除数,继续尝试减去直到被除数小于除数。

   public static int  divide2( int dividend , int divisor){
        // 处理特殊情况
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE;
        }
        // 符号处理
        boolean negative = (dividend < 0) ^ (divisor < 0);
        long ldividend = Math.abs((long)dividend);
        long ldivisor = Math.abs((long)divisor);
        long result = 0;

        while (ldividend >=ldivisor){
            long tempDivisor = ldivisor , multilpe  = 1;
            //将除数加倍,并记录加的倍数
            while (ldividend >= tempDivisor+tempDivisor){
                tempDivisor+= tempDivisor; //加倍除数
                multilpe+= multilpe;  //累加倍数
            }
            ldividend-=tempDivisor; //减去加倍后的除数
            result+=multilpe;
        }
        return negative ? (int)-result : (int)result;
    }

内层 while 循环,确定能够从被除数中减去的最大的除数的倍数。

这个循环通过不断地加倍除数(乘以 2),寻找最接近且不超过被除数的那个除数的倍数。这个过程实际上是在模拟除法的过程,尝试找到一个合适的倍数,使得除数 × 倍数 ≤ 被除数,且尽可能地大。

while (ldividend >= tempDivisor + tempDivisor) {
    tempDivisor += tempDivisor; // 加倍除数
    multiple += multiple; // 记录加倍次数
}
  1. tempDivisor 从原始的除数开始,每次循环加倍(相当于tempDivisor * 2),试图接近但不超过 ldividend。
  2. multiple 记录了加倍的次数,也就是说,如果 tempDivisor 加倍了,那么 multiple 也加倍,表示除数增加了多少倍。

外层 while 循环,使用上一个循环确定的最大倍数来更新被除数,并累加到最终结果中。

当第一个循环结束后,我们找到了最接近且不超过被除数的那个除数的倍数。然后,我们从被除数中减去这个加倍后的除数,并将对应的倍数累加到最终结果中。如果更新后的被除数仍然大于或等于原始除数,这个过程会重复进行。

ldividend-=tempDivisor;//减去加倍后的除数
result+=multiple;//累加倍数

  1. ldividend -= tempDivisor;更新被除数,减去了找到的最大可减的加倍后的除数。
  2. result += multiple;将这一步中减去的除数的倍数累加到 result 中,因为这表示了我们在这一步中“除以了多少个”除数。

来看一下运行效率,还挺高:

分析 3

减法和倍增的另外一种变体就是使用位移,我们来看一下题解:

    public int divide3(int dividend, int divisor) {
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE; // 防止溢出
        }
        boolean negative = (dividend < 0) ^ (divisor < 0);
        long ldividend = Math.abs((long)dividend);
        long ldivisor = Math.abs((long)divisor);

        long result = 0;
        while (ldividend >= ldivisor) {
            long temp = ldivisor, multiple = 1;
            while (ldividend >= (temp << 1)) {
                temp <<= 1;
                multiple <<= 1;
            }
            ldividend -= temp;
            result += multiple;
        }

        return negative ? (int)-result : (int)result;
    }

在二进制表示中,每向左移动一位,其实就等价于将这个数乘以 2。所以,我们可以通过位移来实现倍增。

while (ldividend >= (temp << 1)) {
    temp <<= 1;
    multiple <<= 1;
}

假设 temp 的值为 3,其二进制表示为 0011(这里为了简化,只展示了四位二进制)。当执行 temp << 1 操作时,temp 的二进制表示会向左移动一位,变为 0110,此时 temp 的值就变成了 6。可以看到,3 * 2 = 6,这正是位左移一位的效果。

分析 4

我们还可以使用二分法来实现除法,这个方法是最优的,也是最常用的。

二分法的思路是:我们可以将除数不断地向左移位,直到它大于被除数。因为这样可以最大限度地减小被除数,使得被除数减去除数的次数最少。

我们来看一下题解:

class Solution {
    public int divide(int dividend, int divisor) {
        // 处理特殊情况:Integer.MIN_VALUE除以-1会溢出
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE;
        }
        // 使用异或运算确定结果的正负性,不同号为负
        boolean negative = (dividend < 0) ^ (divisor < 0);
        // 将被除数和除数都转换为正数,使用long类型避免溢出
        long ldividend = Math.abs((long)dividend);
        long ldivisor = Math.abs((long)divisor);

        // 初始化结果变量
        long result = 0;
        // 从最高位开始检查,对32位整数而言,这是31位
        for (int i = 31; i >= 0; i--) {
            // 检查ldividend右移i位后是否仍然大于等于ldivisor
            if ((ldividend >> i) >= ldivisor) {
                // 如果是,说明在2^i的位置上有一个ldivisor,将2^i累加到结果中
                result += 1L << i;
                // 同时,从ldividend中减去ldivisor左移i位的值,即减去了ldivisor的2^i倍
                ldividend -= ldivisor << i;
            }
        }

        // 根据之前确定的正负性,返回正确的结果
        return negative ? (int)-result : (int)result;
    }
}
public int divide4(int dividend, int divisor) {
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE; // 防止溢出
        }
        boolean negative = (dividend < 0) ^ (divisor < 0);
        long ldividend = Math.abs((long)dividend);
        long ldivisor = Math.abs((long)divisor);

        long result = 0;
        //从最高位开始检查,对32位整数而言,这是31位
        for (int i  = 31 ;i >=0 ; i--){
            //检查ldividend右移i位后是否仍大于等于ldivisor
            if (ldividend>>i >= ldivisor){
                //如果是,说明在i^2的位置上有一个ldivisor,将i^2累加到结果中
                result+=1 << i ;
                ldividend-= ldivisor << i;

            }
        }
        // 根据之前确定的正负性,返回正确的结果
        return negative ? (int)-result : (int)result;
    }

这个题解虽然没有直接采用传统的二分搜索算法框架,但它实质上利用了二分思想的核心——每次操作都在减少搜索范围,逐步逼近目标值。

在这个问题中,目标是找到一个数x,使得x * divisor ≈ dividend。通过位操作,每次将dividend右移,实际上是在缩小搜索范围。

这个过程可以看作是对商的二进制表示的逐位确认:

  1. 高位到低位逼近:从dividend的最高位开始,逐步向下检查每一位,看在当前位上divisor是否能"放入"。这里,“放入”的意思是,看乘以2^i(通过左移i位实现)后的divisor是否不超过当前的dividend。这相当于在二分搜索的每一步中,决定这一位上的商是0还是1。
  2. 动态调整搜索范围:通过不断地调整ldividend(即减去已经确定能够放入的divisor的倍数),实际上是在缩小搜索范围,因为每次操作后的ldividend代表了尚未被除尽的余数。

总结

在数学中,加法是加减乘除的基础,减法是加法的逆运算,乘法是加法的倍增,除法是乘法的逆运算。

两数相乘,可以用加法来实现,两数相除,可以用减法来实现,这是最基本的思路。

比如说 10/2,我们可以用 10-2-2-2-2-2=0 来实现,这样就得到了商 5。

比如说 10/3,我们可以用 10-3-3-3-1=0 来实现,这样就得到了商 3 余 1。

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

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

相关文章

【yolov5小技巧(1)】---可视化并统计目标检测中的TP、FP、FN

文章目录 &#x1f680;&#x1f680;&#x1f680;前言一、1️⃣相关名词解释二、2️⃣论文中案例三、3️⃣新建相关文件夹四、4️⃣detect.py推理五、5️⃣开始可视化六、6️⃣可视化结果分析 &#x1f440;&#x1f389;&#x1f4dc;系列文章目录 嘻嘻 暂时还没有~~~~ &a…

OpenCV4.9开发之Window开发环境搭建

1.打开OpenCV所在github地址 2.点击opencv仓库,进入仓库详情,点击右下方的OpenCV 4.9.0进入下载页面 3.点击opencv-4.9.0-windows.exe下载 开始下载中... 下载完成 下载完成后,双击运行解压,默认解压路径,修改为c:/

UE4_材质节点

UE4_材质节点 2017-12-07 13:56 跑九宫格 跑UV 评论(0)

spring总结-基于XML管理bean超详细

spring ioc总结-基于XML管理bean 前言实验一 [重要]创建bean1、目标和思路①目标②思路 2、创建Maven Module3、创建组件类4、创建spring配置文件7、无参构造器8、用IOC容器创建对象和自己建区别 实验二 [重要]获取bean1、方式一&#xff1a;根据id获取2、方式二&#xff1a;根…

LabVIEW专栏五、网口

该节目标编写一个网口调试VI。 上一章是串口&#xff0c;这章介绍网口的写法。 一、网口硬件 1.1、上位机网口 1.2、网口线 由线缆和水晶头组成&#xff0c;现在一般用5类和超5类的网线 1.3、接线方式 忽略&#xff0c;这里加上这点为了提醒一个硬件和上位机连接&#xf…

保健品wordpress外贸模板

保健品wordpress外贸模板 健康保养保健品wordpress外贸模板&#xff0c;做大健康行业的企业官方网站模板。 https://www.jianzhanpress.com/?p3514

Vue.js---------Vue基础

能够说出Vue的概念和作用能够使用vue/cli脚手架工程化开发能够熟练Vue指令 一.vue基本概念 1.学习vue Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 渐进…

高分卫星助力台湾省花莲县地震应急救援

4月3日7时58分&#xff0c;在台湾省花莲县海域&#xff08;北纬23.81度&#xff0c;东经121.74度&#xff09;发生7.3级地震&#xff0c;震源深度12公里。接中国地震局地震预测研究所应急需求&#xff0c;国家航天局对地观测与数据中心&#xff08;以下简称“中心”&#xff09…

[技巧] 逆序对问题 的 分治解法

分治核心思想&#xff1a; 分解&#xff08;Divide&#xff09;&#xff1a;将原问题分解成一系列子问题。这些子问题应该是原问题的较小版本。解决&#xff08;Conquer&#xff09;&#xff1a;递归地解决这些子问题。如果子问题的规模足够小&#xff0c;则直接求解。合并&am…

java自动化-03-04java基础之数据类型举例

1、需要特殊注意的数据类型举例 1&#xff09;定义float类型&#xff0c;赋值时需要再小数后面带f float num11.2f; System.out.println(num1);2&#xff09;定义double类型&#xff0c;赋值时直接输入小数就可以 3&#xff09;另外需要注意&#xff0c;float类型的精度问题…

QT 实现无边框可伸缩变换有阴影的QDialog弹窗

实现无标题栏窗口的拖拽移动、调节窗口大小以及边框阴影效果。初始化时进行位或操作&#xff0c;将这些标志合并为一个值&#xff0c;并将其设置为窗口的标志。这些标志分别表示这是一个对话框、无边框窗口、有标题栏、有最小化按钮和最大化按钮。 setWindowFlags(Qt::Dialog |…

数据结构——图的概念,图的存储结构,图的遍历(dfs,bfs)

目录 1.图的定义和术语 2.案例引入 1.六度空间理论 3.图的类型定义 4.图的存储结构 1.邻接矩阵 1.无向图的邻接矩阵表示法 2.有向图的邻接矩阵表示法 3.网&#xff08;有权图&#xff09;的邻接矩阵表示法 代码示例&#xff1a; 2.采用邻接矩阵表示法创建无向图…

【Qt 学习笔记】认识QtSDK中的重要工具

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 认识QtSDK中的重要工具 文章编号&#xff1a;Qt 学习笔记 / 03 文章目…

单例模式以及线程安全问题

单例模式的概念 单例模式是指的是整个系统生命周期内&#xff0c;保证一个类只能产生一个实例对象 保证类的唯一性 。 通过一些编码上的技巧&#xff0c;使编译器可以自动发现咱们的代码中是否有多个实例&#xff0c;并且在尝试创建多个实例的时候&#xff0c;直接编译出错。 …

哈佛大学商业评论 -- 第二篇:增强现实是如何工作的?

AR将全面融入公司发展战略&#xff01; AR将成为人类和机器之间的新接口&#xff01; AR将成为人类的关键技术之一&#xff01; 请将此文转发给您的老板&#xff01; --- 本文作者&#xff1a;Michael E.Porter和James E.Heppelmann 虽然物理世界是三维的&#xff0c;但大…

SpringCloud - 如何本地调试不会注册到线上环境(Nacos)?

问题描述 有时候我们需要本地调试注册到 Nacos 上&#xff0c;但是会影响线上服务的 Feign 请求打到本地导致不通影响了线上业务。 原因分析 一般最传统的解决方案就是修改本地 bootstrap.yml 的 spring.cloud.nacos.discovery.namespace spring:application:name: app-serv…

第十二篇【传奇开心果系列】Python自动化办公库技术点案例示例:深度解读Python自动化操作Word

传奇开心果系列博文 系列博文目录Python自动化办公库技术点案例示例系列 博文目录前言一、Python自动化操作Word介绍二、使用python-docx示例代码二、**使用win32com示例代码**三、使用comtypes示例代码四、使用docx-mailmerge示例代码五、基本操作示例代码六、高级操作示例代码…

mysql 磁盘空间100%

MySQL大事务可能会导致过多的占用临时文件&#xff0c;导致磁盘空间撑满的问题 本例说明下binlog cache产生的临时文件 案例复现 调小binlog_cache_size&#xff0c;让DML使用临时文件 使用存储过程模拟大事务 创建表 create table t1( id int AUTO_INCREMENT, name varchar…

文献阅读:将条形码神经解剖学与空间转录分析相结合,可以识别投射神经元相关基因

文献介绍 「文献题目」 Integrating barcoded neuroanatomy with spatial transcriptional profiling enables identification of gene correlates of projections 「研究团队」 Anthony M. Zador&#xff08;美国冷泉港实验室&#xff09; 「发表时间」 2021-05-10 「发表期…

【蓝桥杯-枚举模板题】

蓝桥杯-枚举模板题 滑雪课程设计 P3650新的家乡 P8587枚举子集 B3622Air Cownditioning P9011 滑雪课程设计 P3650 核心的思路是把数据规定在[i,i17]里&#xff0c;不够的补&#xff0c;过大的减。枚举i以求最少的钱。 #include<bits/stdc.h> using namespace std;int n…