作差模拟两数相除(29. 两数相除)

本文探讨了一种解决LeetCode上的整数除法问题的方法,通过避免使用乘法、除法和模运算符。初始的简单实现通过不断作差导致超时,而改进后的解决方案利用减数翻倍加快了计算速度,从而提高了效率。通过递归和减数翻倍,实现了在32位整数限制内的正确除法计算,并避免了溢出情况。

原题链接:

https://leetcode-cn.com/problems/divide-two-integers/

题目描述

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

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

整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2

示例1:

输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = truncate(3.33333…) = truncate(3) = 3

示例2:

输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = truncate(-2.33333…) = -2

提示:

  • 被除数和除数均为 32 位有符号整数。
  • 除数不为 0。
  • 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231 − 1]。本题中,如果除法结果溢出,则返回 231 − 1。

思路方法:

最简单直接的一种方法就是做差法,先根据被除数与除数确定商的最终符号,然后被除数与除数均取绝对值,循环作差,直到被除数不够减位置,作差的次数即为商的绝对值。

着实简单粗暴,超时也在所难免!

超时代码:

class Solution {
public:
    int divide(int dividend, int divisor) {
        int flag = 1;
        if((dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0)){
            flag = -1;
        }
        long long a = dividend,b = divisor,ans = 0;
        a = fabs(a);
        b = fabs(b);
        while(a >= b){
            ans++;
            a -= b;
        }
        if(flag * ans > INT_MAX || flag * ans < INT_MIN){
            return INT_MAX;
        }
        return flag * ans;
    }
};

那么如何避免超时呢?这是个问题。😉
如果每作差一次就将减数翻倍,那么作差的效率不就会成指数倍速的增加。
话不多说,都在这代码里头了!!!

AC代码:

class Solution {
public:
    long long dg(long long a, long long b){
        long long ans = 0,t = 1,x = b;
        if(a < b){
            return 0;
        }
        while(a >= x){
            a -= x;
            x += x;//减数翻倍 
            ans += t;
            t += t;//商增加的结果也会翻倍 
        }
        return ans + dg(a,b);//不够减时,减数还原为原结果再次递归,直到还原后的结果也不够减 
    }
    int divide(int dividend, int divisor) {
        int flag = 1,z = 1;
        if((dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0)){//确定最终结果的符号 
            flag = -1;
        }
        long long a = fabs(dividend), b = fabs(divisor),ans;//若使用int,那么当数据为INT_MIN时,加绝对值便会越界。 
        ans = dg(a,b);//递归作差 
        if(flag * ans > INT_MAX || flag * ans < INT_MIN){
            return INT_MAX;
        }
        return flag * ans;
    }
};
<think>我们需要实现两数相除的操。在JavaStream中,如果我们要对两个数进行相除,通常是在处理流中的元素时进行。例如,我们有一个包含数字的流,我们想对流中的每两个数字进行相除。但是,流操通常是单元素处理(如map)或聚合操(如reduce),对于每两个元素的操,可能需要一些额外的处理。然而,根据用户的问题,他引用的例子中有一个是计算两个数字相除小数结果并保留小数。因此,我们可能需要考虑两种情况:1.流中的元素是成对的,我们想要将每对中的两个数相除。2.我们有两个独立的流(或两个集合),想要将两个流中对应位置的元素相除。但是,在标准的StreamAPI中,流是单元素的序列,并没有直接提供成对处理两个流的方(除了zip操,但Java标准库没有提供zip)。不过,我们可以通过一些技巧来实现。根据用户的问题,他可能希望看到如何用流来实现两个数的相除,然后扩展到多个数的成对相除。但是,用户的问题具体是“两数相除”,可能是指单个操,也可能是指流中每两个元素相除。由于用户之前引用的例子中有一个是计算两个数字相除,并保留小数结果,所以我们先考虑两个数的相除。然而,在流中,如果只有两个数,我们可以用reduce来模拟除法?但是除法没有结合性,所以reduce通常不适用于除法(因为reduce需要满足结合性)。另一种可能:用户可能想对流中的元素连续进行除法运算,比如用第一个元素去除以第二个元素,然后再用结果除以第三个元素,以此类推。但是,连续除法也不是一个常见的操,而且顺序很重要。考虑到用户引用的第一个例子中,使用了reduce进行累加(加),然后提到了计算两个数字相除。所以,我们猜测用户可能希望看到如何用流操来实现两个数字的除法?但两个数字的除法很简单,不需要用流。所以更可能是希望对流中的每两个元素进行相除。但是,流操一般是对单个元素进行转换,或者进行聚合。如果我们有这样一个需求:有一个列表,希望计算相邻两个元素的商(即后一个元素除以前一个元素,或者前一个除以后一个)?比如在金融领域计算每日的收益率。我们以计算后一个除以前一个为例:假设有一个数字列表[a0,a1,a2,a3],我们想得到[a1/a0,a2/a1,a3/a2]。那么,我们可以使用IntStream.range(0,list.size()-1)来生成索引流,然后对每个索引i,用list.get(i+1)除以list.get(i)。示例代码:假设我们有一个BigDecimal的列表(因为涉及到精确计算,所以我们用BigDecimal):List<BigDecimal>numbers=...;我们可以这样写:List<BigDecimal>ratios=IntStream.range(0,numbers.size()-1).mapToObj(i->numbers.get(i+1).divide(numbers.get(i),scale,roundingMode)).collect(Collectors.toList());但是,这里我们使用了索引,这打破了流的顺序处理,但这是可行的。另外,如果用户想对两个独立的流进行对应元素的相除,那么我们需要将两个流合并在一起。但Java标准库没有直接支持。我们可以使用流处理索引,然后通过索引同时取两个列表中的元素。例如:有两个列表listA和listB,长度相同。我们想计算listA[i]/listB[i]。我们可以:List<BigDecimal>results=IntStream.range(0,listA.size()).mapToObj(i->listA.get(i).divide(listB.get(i),scale,roundingMode)).collect(Collectors.toList());用户的问题描述是“两数相除”,但是结合引用,可能是希望在一个流中进行操。由于用户没有明确说明,我们可以给出两种情况的示例。但是,用户最后的问题明确要求:“我想了解如何使用JavaStream实现两数相除的操”,并且要求示例代码。另外,在用户引用的部分,有一个例子是计算两个数字相除小数结果并保留小数,所以我们重点展示除法以及结果保留小数。我们将给出两个示例:1.单个除法(两个数字)使用流?其实没有必要,但我们可以创建一个只包含两个元素的流,然后进行除法。2.成对除法(比如两个列表对应位置相除,或者一个列表相邻元素相除)。由于单个除法没有必要用流,所以我们重点放在成对除法。根据用户引用的例子,我们使用BigDecimal进行精确的除法,并设置小数位数和舍入模式。示例1:对两个BigDecimal数进行相除(单个操,不使用流的情况)BigDecimala=newBigDecimal("10.0");BigDecimalb=newBigDecimal("3.0");BigDecimalresult=a.divide(b,2,RoundingMode.HALF_UP);//保留两位小数,四舍五入示例2:使用流处理相邻元素的除法(如计算收益率)List<BigDecimal>prices=Arrays.asList(newBigDecimal("100"),newBigDecimal("110"),newBigDecimal("121"));List<BigDecimal>returns=IntStream.range(0,prices.size()-1).mapToObj(i->prices.get(i+1).divide(prices.get(i),4,RoundingMode.HALF_UP).subtract(BigDecimal.ONE)).collect(Collectors.toList());注意:这里计算的是收益率,即(后价-前价)/前价,所以除完后减去1。如果用户只需要比率,则不用减1。示例3:两个独立列表对应元素相除(如计算多个比率)List<BigDecimal>numerators=...;//分子列表List<BigDecimal>denominators=...;//分母列表//假设两个列表长度相同List<BigDecimal>ratios=IntStream.range(0,numerators.size()).mapToObj(i->numerators.get(i).divide(denominators.get(i),4,RoundingMode.HALF_UP)).collect(Collectors.toList());现在,用户要求示例代码,我们就写一个完整的示例代码,包括两种情况。但是,为了简洁,我们将在一个main方中展示。注意:在引用中,用户提到保留一位小数,并使用了ROUND_DOWN(向下取整)。在我们的示例中,将使用不同的舍入模式,用户可以根据需要调整。下面我们给出示例代码:</think> ### 如何使用 Java Stream 实现两数相除 在 Java Stream 中实现两数相除,需要结合 `map()` 或 `reduce()` 进行数值转换和计算。以下是三种典型场景的示例代码(使用 `BigDecimal` 保证精度): #### 1. 流中两个数值直接相除 ```java List<BigDecimal> numbers = Arrays.asList( new BigDecimal("15.5"), new BigDecimal("3.0") ); BigDecimal result = numbers.stream() .reduce((a, b) -> a.divide(b, 2, RoundingMode.HALF_UP)) .orElse(BigDecimal.ZERO); System.out.println("15.5 ÷ 3.0 = " + result); // 输出: 5.17 ``` #### 2. 对象列表的字段相除(例如:人均金额计算) ```java class Person { String name; BigDecimal money; // 构造方和getter } List<Person> persons = Arrays.asList( new Person("A", new BigDecimal("100.00")), new Person("B", new BigDecimal("50.00")) ); // 计算人均金额(总金额÷人数) BigDecimal avgMoney = persons.stream() .map(Person::getMoney) .reduce(BigDecimal::add) .map(total -> total.divide( new BigDecimal(persons.size()), 1, RoundingMode.DOWN )) .orElse(BigDecimal.ZERO); System.out.println("人均金额: " + avgMoney); // 输出: 75.0 ``` #### 3. 流中成对元素相除 ```java List<BigDecimal[]> pairs = Arrays.asList( new BigDecimal[]{new BigDecimal("10.0"), new BigDecimal("2.0")}, new BigDecimal[]{new BigDecimal("9.0"), new BigDecimal("3.0")} ); List<BigDecimal> results = pairs.stream() .map(pair -> pair[0].divide(pair[1], 3, RoundingMode.HALF_EVEN)) .collect(Collectors.toList()); // 输出: [5.000, 3.000] results.forEach(System.out::println); ``` ### 关键注意事项 1. **精度控制**:必须指定 `scale`(小数位数)和 `RoundingMode`(舍入模式),否则除不尽时会抛出 `ArithmeticException` 2. **空值处理**:使用 `orElse()` 处理可能的空结果 3. **并行流安全**:`BigDecimal` 是线程安全的,适合并行流计算 4. **对象字段处理**:结合 `map()` 提取对象数值字段(如 `Person::getMoney`) > 通过 `reduce()` 实现聚合计算(如总金额),再通过 `map()` 进行除法,这是 Stream 处理除法运算的通用模式[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WTY2002

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值