数据结构和算法笔记(1)数论相关

最近在刷编程题,发现了许多杂七杂八的知识点,稍微归类并记录下来方便后续查阅。

本文的目录如下:

1.组合数    2.快速幂    3.大数取模(乘法逆元和费马小定理)    4.菲波拉契数列

 

1.组合数的求法

组合数$C_n^k$,为了方便也写作C(n, k),表示从n个不同元素中取出k (k≤n)个元素的所有组合个数

我们知道存在公式C(n, k) = [n*(n-1)*(n-2)*...*(n-k+1)] / [1*2*3*...*k],直接的做法是把该式编程实现一下,时间复杂度也不会很高。但是这样做存在一个问题:数值溢出。因此我们需要进行一些改进,其实也就是不先乘后除,而是边乘边除,这就避免了连乘时存在n的k次方导致数值溢出的情况。因为比较简单就直接上代码了。

long long combination(long long n, long long k) {
	k = min(k, n - k);
	long long A = 1;
	for (long long i = 1; i <= k; ++i, --n) {
		A *= n;
		A /= i;
	}
	return A;
}

这里有个困惑的点在于,为什么A /= i正好能整除呢?我们注意到,i是从1到k变化的,首先,i=1时肯定可以整除;i=2时,此时分子上为n*(n-1),一定存在因子2;i=3时,分子上已经乘了 n*(n-1)*(n-2),一定存在因子3. 依此类推,我们就可以得出A /= i可以整除的结论。

通常情况下上面的算法已经够用了,但是似乎还有一些特殊案例不能通过,所以还看到有用最大公因数化简的实现:

long long gcd(long long a, long long b) {
	return !b ? a : gcd(b, a%b);
}

long long combination(long long n, long long k) {
	k = min(k, n - k);
	long long A = 1, C;
	for (long long i = 1; i <= k; ++i, --n) {
		C = gcd(i, n);
		A *= n / C;
		A /= i / C;
	}
	return A;
}

 

2.快速幂

假设我们要计算x^{^{n}},记作pow(x, n),其中x为浮点数,n为有符号整数。我们可以直接对n个x连乘得到结果,时间复杂度为O(n),但是显然有更好的方法。由于x^{^{n}}=x^{^{n/2}} * x^{^{n/2}},我们可以把复杂度优化为O(log n). 代码如下:

double pow(double x, int n) {
    long long N = n;
    if (N < 0) {
        x = 1 / x;
        N = -N;
    }
    double ans = 1;
    double current_product = x;
    for (long long i = N; i ; i /= 2) {
        if ((i % 2) == 1) {
            ans = ans * current_product;
        }
        current_product = current_product * current_product;
    }
    return ans;
}

此外,还有一种情况是快速幂+取模,记作powm(x, n, m),即x的n次方再对m取模,其中x、n、m均为无符号整数。代码如下:

typedef unsigned long long uLL;

uLL powm(uLL x, uLL n, uLL m) {
	uLL ans = 1;
	while(n > 0) {
		if(n & 1) ans = ans * x % m;
		x = x * x % m;
		n >>= 1;
	}	
	return ans;
}

如果是用python的话,可以直接调用内置的pow函数,也是快速幂实现,并且带有取模的参数。

 

3.大数取模

首先介绍同余的概念。当两个整数除以同一个正整数,若得相同余数,则二整数同余。换句话说,如果a%m==b%m,则称a,b同余,记作a≡b(mod m).

同余有很多特殊的性质,这里就不展开了,后续碰到相关题目再补充。

回到大数取模的问题上,常见的取模公式有:

        (a + b) % m = (a % m + b % m) % m

        (a - b) % m = (a % m - b % m) % m

        (a * b) % m = ((a % m) * (b % m)) % m

        a ^ b % m = ((a % m) ^ b) % m

特殊的是,除法的取模,即(a / b) % m的求法,和上面的规律不同。我们需要用到乘法逆元和费马小定理的知识。

费马小定理:假如 p 是质数,那么 a ^ (p-1) ≡ 1 (mod p) 。

推论:b ^ (p - 2) % p 即为 b mod p 的乘法逆元,也就是说:

除法取模:(a / b) % m = (a * b ^ (m - 2)) % m.

注意这里要求m为质数,通常的算法题里的模都等于10e9+7,正好是质数。于是我们可以把除法取模转换成乘法和幂取模,而计算幂可以用到上面的快速幂算法。

 

4.菲波拉契数列

所谓菲波拉契数列,就是1,1,2,3,5,8,13,21,...,按照规律f(n) = f(n-1) + f(n-2)推算的数列。要计算第i个斐波那契数,即f(i),直接套公式递归会有很多重复计算,肯定不是最优的,常规的做法是用两个变量记录上一个数和上上一个数,然后递推更新,时间复杂度为O(n). 但是最近发现,居然还有两种O(log(n))的算法:Binets方法斐波那契公式

篇幅有限就不写了,直接贴leetcode链接吧,也就是爬楼梯问题的题解。

https://leetcode-cn.com/problems/climbing-stairs/solution/pa-lou-ti-by-leetcode/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章