快速幂 - 分治

介绍快速幂之前我们先来看一道例题

例题:

比如说要求解2的16次方等于多少?

常规方法:

先定义一个变量 mul,一开始 mul 为 2的0次方 = 1,然后将 mul * 2 循环 16 次。

/**
 * 快速幂 - 分治
 */
public class QuickPowLeetcode50 {

    public static double myPow(int x, int n){
        double mul = 1;
        for (int i = 0; i < n; i++) {
            mul *= x;
        }
        return mul;
    }

    public static void main(String[] args) {
        System.out.println(myPow(2, 10));  // 1024.0
    }
}

但是这种方法效率比较低,要做很多次乘法,时间复杂度高。

优化:

这题我们可以采用分治的思想:

首先, 2的16次方可以拆成两个2的8次方, 2的8次方又可以拆成两个2的4次方, 2的4次方可以拆成两个2的2次方,最后2的2次方可以拆成 2 x 2。

我们可以把它拆成左右两边都对称的二叉树。

从最底层开始看(右子树开始),2 x 2 = 4(第一次乘),当右子树计算出来后,因为左右子树是对称的,对应的左子树就不用重复计算了, 4 x 4 = 16 (第二次乘),然后 16 x 16 = 256(第三次乘),256 x 256 = 65536(第四次乘)。

我们运用分治这种思想只进行了4次乘法运算,相比之前用常规方法循环乘16次效率提高了不少。所以它叫快速幂。

使用快速幂我们还需要考虑一种情况,前面的幂都是偶数,所以可以直接把左右两边的值相乘。

幂如果是奇数的话,就是另外一种情况了,如下图:

比如上图中的 2^10 拆成 两个2^5,然后2^5可以拆成 2 * 两个2^2, 接着 2^2又可以拆成2 x 2。

代码实现:
/**
 * 快速幂 - 分治
 */
public class QuickPowLeetcode50 {

  
    public static double myPow2(int x, int n) {
        double mul = 1;
        for (int i = 0; i < n; i++) {
            mul *= x;
        }
        return mul;
    }

    public static double myPow(int x, int n) {
        if (n == 0) {
            return 1.0;
        }
        //首先找到递归出口
        if (n == 1) {
            return x;
        }
        double y = myPow(x, n / 2);
        if (n % 2 == 1) {//奇数
            return y * y * x;
        } else {//偶数
            return y * y;
        }
    }

    public static void main(String[] args) {
        System.out.println(myPow(2, 10));  // 1024.0
        //System.out.println(myPow(2.1, 3)); // 9.261
        System.out.println(myPow(2, -2)); // 0.25
        System.out.println(myPow(2, 0)); // 1.0
        System.out.println(myPow(2, -2147483648)); // 1.0
    }
}

当然,这样的代码拿去leetcode上去跑,有几个测试用例不能通过。

首先,幂如果为负数的情况我们没有考虑到,其次,幂为整数型最小负数的情况也跑不出来。

如果幂为负数,我们可以把它当作正数来求,最后对计算结果求倒数就可以了。

因为整型int 的最小值为 -2147483648,但是最大值为 2147483647,这就比较恶心了,负负得正会导致溢出,在这里leetcode官方也没给出什么好的解决办法,把数据类型int 改成 long型就可以了。

优化后:
/**
 * 快速幂 - 分治
 */
public class QuickPowLeetcode50 {

   

    public static double myPow(int x, int n){
        long p = n;
        if(p < 0){
            p = -p; //负负得正
        }
        double r = myPowPositive(x, p);
        return n < 0 ? 1 / r : r;
    }

    public static double myPowPositive(int x, long n) {
        if (n == 0) {
            return 1.0;
        }
        //首先找到递归出口
        if (n == 1) {
            return x;
        }
        double y = myPowPositive(x, n / 2);
        if (n % 2 == 1) {//奇数
            return y * y * x;
        } else {//偶数
            return y * y;
        }
    }

    public static void main(String[] args) {
        System.out.println(myPow(2, 10));  // 1024.0
        //System.out.println(myPow(2.1, 3)); // 9.261
        System.out.println(myPow(2, -2)); // 0.25
        System.out.println(myPow(2, 0)); // 1.0
        System.out.println(myPow(2, -2147483648)); // 1.0
    }
}

接下来再介绍一种判断一个数是奇数还是偶数的方法:

我们可以用位运算,看一下 1,2,3,4,5,6,7 对应的二进制数。

可以看到,奇数的二进制的最后一位永远是1,而偶数的最后一位是0,我们只要拿到它们的最后一位,判断是0还是1即可。

我们可以把它和1做按位与,就能拿到二进制数的最后一位。

完整代码:
/**
 * 快速幂 - 分治
 */
public class QuickPowLeetcode50 {

   

    public static double myPow(double x, int n){
        long p = n;
        if(p < 0){
            p = -p; //负负得正
        }
        double r = myPowPositive(x, p);
        return n < 0 ? 1 / r : r;
    }

    public static double myPowPositive(double x, long n) {
        if (n == 0) {
            return 1.0;
        }
        //首先找到递归出口
        if (n == 1) {
            return x;
        }
        double y = myPowPositive(x, n / 2);
        if ((n & 1) == 1) {//奇数
            return y * y * x;
        } else {//偶数
            return y * y;
        }
    }

    public static void main(String[] args) {
        System.out.println(myPow(2, 10));  // 1024.0
        System.out.println(myPow(2.1, 3)); // 9.261
        System.out.println(myPow(2, -2)); // 0.25
        System.out.println(myPow(2, 0)); // 1.0
        System.out.println(myPow(2, -2147483648)); // 1.0
    }
}

感谢阅读!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值