LeetcCode困难题——奇妙序列扩展欧几里得算法解法

题目描述

请你实现三个 API append,addAll 和 multAll 来实现奇妙序列。

请实现 Fancy 类 :

  • Fancy() 初始化一个空序列对象。
  • void append(val) 将整数 val 添加在序列末尾。
  • void addAll(inc) 将所有序列中的现有数值都增加 inc 。
  • void multAll(m) 将序列中的所有现有数值都乘以整数 m 。
  • int getIndex(idx) 得到下标为 idx 处的数值(下标从 0 开始),并将结果对 109 + 7 取余。如果下标大于等于序列的长度,请返回 -1 。

题目链接力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

思路

本题难点在于通过时间设限和数值太大溢出问题,如果采用暴力解法,在每次addAll和multAll时遍历所有元素,那时间复杂度为O(n),会无法通过本题,对于值溢出的情况,需要在每次涉及到运算的地方取模P,也是为了符合题目要求,先考虑初步的思路,目前只考虑在data数组中已元素且在此之前还没有进行任何addAll和multAll操作的情况,此时再开始进行加与乘操作,如果想在O(1)的情况下完成上面的函数,可以通过初等变换一个二阶方阵A来实现上述的加与乘操作的记录,a为待加元素,m为待乘元素

此时获取元素值时,将矩阵A中的am取出与数组data中的元素e运算,为了防止溢出,本题后续所有运算后都会取模P,即有下面的等式 

 

初始时待乘变量为1,待加变量0,因此该方阵初始时如下 

 

如果要进行加c操作,就让其左乘一个初等加矩阵 

 如果是乘c操作,就让其数左乘一个初等乘矩阵

 

 通过简单的线性变换来降低addAll和multAll的时间复杂度,这是初步思路,但是方阵A会随着addAll和multAll不断变化,data中的元素数量也是动态递增的,不难看出,data中放入的元素e如果要获得它的待加元素和待乘元素,只有在其被加入之后的新的addAll和multAll操作才是有效的,如果元素ei加入之前的方阵为Ai,当前最新的方阵为An,那就需要解出通过新的加与乘的线性变换方阵B,有

解出矩阵

 

为  

这样就得到了每一个元素ei的待加元素和待乘元素,其中的逆元,因为本题中所有的运算都有对P取余的缘故,,对于求逆元,有两种算法,一种是费马小定理,若P为素数,gcd(m,p)=1,则 

要求m的逆元就有 

显然本题中的P为素数,要求,可以采用快速幂算法,时间复杂度为O(logP),本题C++代码如下 

long Pinv(long m)	{
	long ans = 1, p = P - 2;
	while (p) {
		if (p & 1)
			ans = ans * m % P;
		m = m * m % P;
		p >>= 1;
	}
	return ans;
}

另一种方法是用扩展欧几里算法来求逆元,本题使用的就是该算法,通过裴蜀定理可知, x,y∈Z,使得 

题中gcd(m,P)≡1,不难看出,对公式(3-11)两边取余时有,这里的t就是本题要求的逆元,同样采用递推的思路来减少时间和空间开销,为了实现递推,可以设,商为q有 

qn时结束循环,根据公式(3-11)有集合S={rᵢ|rᵢ=sᵢr₀+tᵢr₁, i∈Z},因此,可以通过递推归纳法来得到sᵢ的前后关系 

 

将1)和2)代入推出下面的r2

整理得如下方程

其中不为零,故,由归纳法得到,有了s和t的表达式,即可得出扩展欧几里得算法的递推形式

long Einv(long m) {
	long x = 1, y = 0, p = P, temp;
	while (m) {
		temp = x;
		x = y - x * (p / m);
		y = temp;
		temp = p % m;
		p = m;
		m = temp;
	}
	return (y < 0) ? y + P : y;
}

该算法的时间复杂度为O(log min{m,P}),因此相较于费马小定理的快速幂,可以更快的得出逆元,本题在append函数实现求逆操作并整合到data中,并且在getIndex中获取与data所在下标中的元素参与运算得到最终结果,要实现它,根据本题公式(3-8)有

 

因此append函数中要实现的计算就是

在getIndex函数中要实现的计算就是

通过上面的解法,可以在O(log min{m,P})的时间内完成append操作,而其余的操作均为O(1),费马小定理快速幂求逆元也能在O(logP)的时间内完成append两者各有优劣。

 代码

 

#include <iostream>
#include <vector>
using namespace std;

class Fancy {
private:
	static const long P = 1000000007;
	vector<long> data;
	long mult, add;

public:
	Fancy() {
		mult = 1;
		add = 0;
	}

	// 扩展欧几里得算法求逆元
	long Einv(long m) {
		long x = 1, y = 0, p = P, temp;
		while (m) {
			temp = x;
			x = y - x * (p / m);
			y = temp;
			temp = p % m;
			p = m;
			m = temp;
		}
		return (y < 0) ? y + P : y;
	}

	//费马小定理快速幂求逆元
	long Pinv(long m)	{
		long ans = 1, p = P - 2;
		while (p) {
			if (p & 1)
				ans = ans * m % P;
			m = m * m % P;
			p >>= 1;
		}
		return ans;
	}

	void append(int val) {
		data.push_back((val - add + P) % P * Einv(mult) % P);
    }

    void addAll(int inc) {
		add = (add + inc) % P;
    }

    void multAll(int m) {
		mult = mult * m % P;
		add = add * m % P;
    }

    int getIndex(int idx) {
		if (idx >= data.size())
			return -1;
		return (data[idx] * mult % P + add) % P;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值