快速幂(比赛必备)

快速幂

快速幂一般用来解决 ab % c 的问题,当然计算不模c的情况也行。

1.暴力

 
 // 返回结果a^b % c   (c的范围一般很小所以用了int)
 public long pow(long a, long b, int c)
 {
      long res = 1;
      for (long i = 0 ; i < b; i++)
      {
          res *= a;
      }
      return res % c;
  }

这种方法对于数据范围小的情况还是可以的,但在特殊的情况下b的范围很大,例如 0 < b < 1018

缺陷:

  • 计算中的res很容易超过long

  • 时间复杂度高 O(N)

2.解决溢出问题

取模运算有个性质:(a * b) % p = ((a % p) * (b % p)) % p

下面我们来简单证明下:

设:a = k1 * p + q1 b = k2 * p + q2

(a * b) = (...) * p + q1 * q2

即 (a * b) % p = (q1 * q2) % p (这里不知道q1 * q2是否大于p,所以还要取模一下)

又因为 a % p = (k1 * p + q1) % p = q1 b % p = (k2 * p + q2) % p = q2

所以 (a * b) % p = ((a % p) * (b % p)) % p

但这有什么用呢?

我们之前是( a * a * ...) % c,根据这个性质我们就可以边乘边对c取摸了,这样不就解决了溢出问题了吗

即:ans *= a; ans %= c;

但还有缺陷:

  • 时间复杂度的问题没有解决

3.降低时间复杂度

先看一下我们手算310是怎么算的?

310 = (32)5 = 95 = 9 * 94 = 9 * (92)2 = 9 * 812 ....

根据上面,我们很容易发现规律:

当指数是偶数时,让指数除2,底数平方。 当指数是奇数的时候让res乘一个底数,在让指数除2,底数平方

这个算法是时间复杂度是多少? 每次让指数 b /= 2,显然是O(logN)

4.代码实现

1.详细版

 public long quickPow(long a, long b, int c)
 {
     a %= c; // 防止一开始a过大
     long res = 1; // 其实这里用int也行
     while(b > 0)
     {
         if (b % 2 == 1) // 指数是奇数
         {
             res = (res * a) % c; // 边乘边摸
             a = (a * a) % c;
             b /= 2;
         }
         else // 指数是偶数
         {
             a = (a * a) % c;
             b /= 2;
         }
     }
     return res;
 }

2.简洁版+位运算优化

 public long quickPow(long a, long b, int c)
 {
     a %= c; 
     long res = 1;
     while(b > 0)
     {
         if (b & 1 == 1)  // b % 2 == 1
         {
             res = (res * a) % c;
         }
         a = (a * a) % c; 
         b >>= 1;   // b /= 2;
     }
     return res;
 }

5.课后习题

leetcode :

上一篇:热力学定律的讨论


下一篇:2022-京东-架构工程师-秋招面经