最近在刷编程题,发现了许多杂七杂八的知识点,稍微归类并记录下来方便后续查阅。
本文的目录如下:
1.组合数 2.快速幂 3.大数取模(乘法逆元和费马小定理) 4.菲波拉契数列 5.最大公约数
组合数,为了方便也写作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;
}
假设我们要计算,记作pow(x, n),其中x为浮点数,n为有符号整数。我们可以直接对n个x连乘得到结果,时间复杂度为O(n),但是显然有更好的方法。由于,我们可以把复杂度优化为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函数,也是快速幂实现,并且带有取模的参数。
首先介绍同余的概念。当两个整数除以同一个正整数,若得相同余数,则二整数同余。换句话说,如果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,正好是质数。于是我们可以把除法取模转换成乘法和幂取模,而计算幂可以用到上面的快速幂算法。
所谓菲波拉契数列,就是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/
int gcd(int a, int b){
while (b != 0){
int tmp = a % b;
a = b;
b = tmp;
}
return a;
}