快速幂与矩阵快速幂 Shepard-Wang

title: 快速幂与矩阵快速幂
tags: 算法

推荐阅读和观看:

一 快速幂

我们用两道例题学习快速幂

pow(x, n)

实现 pow(x, n) ,即计算 x 的 n 次幂函数。

示例 1:

输入: 2.00000, 10
输出: 1024.00000

示例 2:

输入: 2.10000, 3
输出: 9.26100

示例 3:

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

说明:

-100.0 < x < 100.0 n 是 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1] 。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/powx-n 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

测试用例思考:

  • 如果幂为负数,转换为正数再计算。但是 -2^31 转为正数会溢出,需要使用 long long 来保存幂
  • 0^01 还是 0 ?
  • 溢出情况如何处理?(和面试官沟通)

算法:快速幂 时间复杂度:O(logn)

题解

参考文章

迭代法:

class Solution {
public:
    double myPow(double x, int n) 
    {
        double ans = 1;
        long long _pow = n;
        if(n < 0)
        {
            x = 1 / x;
            _pow *= -1;
        }
        while(_pow)
        {
            if(_pow & 1) ans *= x;
            x *= x;
            _pow >>= 1;
        }
        return ans;
    }
};

递归法:

class Solution {
public:
    double myPow(double x, int n) 
    {
        long long _pow = n;
        return _pow >= 0 ? FastPow(x, _pow) : 1.0 / FastPow(x, -_pow);
    }
private:
    double FastPow(double x, long long n)
    {
        if(n == 0)
            return 1.0;
        double y = FastPow(x, n / 2);
        return n % 2 == 0 ? y * y : y * y * x; 
    }
};

超级次方

你的任务是计算 ab 对 1337 取模,a 是一个正整数,b 是一个非常大的正整数且会以数组形式给出。

示例 1:

输入:a = 2, b = [3]
输出:8

示例 2:

输入:a = 2, b = [1,0]
输出:1024

示例 3:

输入:a = 1, b = [4,3,3,8,5,2]
输出:1

示例 4:

输入:a = 2147483647, b = [2,0,0]
输出:1198

提示:

1 <= a <= 231 - 1 1 <= b.length <= 2000 0 <= b[i] <= 9 b 不含前导 0

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/super-pow 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

class Solution {
public:
    int superPow(int a, vector<int>& b) 
    {
        return Pow(a, b);
    }
private:
    const int MOD = 1337;
    int Pow(int a, vector<int>& b)
    {
        if(b.empty()) return 1;
        int back = b.back();
        b.pop_back();
        int p1 = fastPow(a, back);
        int p2 = fastPow(Pow(a, b), 10);
        return p1 * p2 % MOD;
    }
    int fastPow(int a, int b)
    {
        int ans = 1;
        int base = a % MOD;
        while(b)
        {
            if(b & 1) ans *= base, ans % MOD;
            base *= base;
            base %= MOD;
            b >>= 1;
        }
        return ans % MOD;
    }
    
};

二 矩阵快速幂

我们可以一道习题来引入矩阵快速幂:

斐波那契额数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1 F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:1

示例 2:

输入:n = 5
输出:5

提示:

0 <= n <= 100

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我们可以使用迭代法用 O(1) 的空间复杂度和 O(n) 的时间复杂度快速解决这个问题。但是我们可以使用矩阵快速幂的方法将时间复杂度降为 :O(logn)

const int MOD = 1000000007;
const int N = 50;

struct mat
{
    int m[N][N];
    
    mat()
    {
        memset(m, 0, sizeof(m));
    }
    
    // 单位矩阵
    void setUnitMat()
    {
        for (int i = 0; i < N; ++i)
            m[i][i] = 1;
    }
};

class Solution {
public:
    int fib(int n)
    {
        if (n <= 1)
            return n;
        mat q;
        q.m[0][0] = q.m[0][1] = q.m[1][0] = 1;
        q.m[1][1] = 0;
        mat ans = fastPow(q, n - 1);

        return ans.m[0][0];
    }
private:
    const int n = 2;

    mat&& fastPow(mat q, int n)
    {
        // ans 初始为单位矩阵
        mat ans;
        ans.setUnitMat();
        while (n > 0)
        {
            if (n & 1)
                ans = matrixMul(q, ans);
            q = matrixMul(q, q);
            n >>= 1;
        }
        return std::move(ans);
    }

    mat&& matrixMul(mat& q, mat& M)
    {
        mat ans;
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j < n; ++j)
            {
                for (int k = 0; k < n; ++k)
                {
                    ans.m[i][j] += q.m[i][k] * M.m[k][j] % MOD;
                }
            }
        }
        return std::move(ans);
    }
};

官方题解

其他例题: