最近在刷編程題,發現了許多雜七雜八的知識點,稍微歸類並記錄下來方便後續查閱。
本文的目錄如下:
1.組合數 2.快速冪 3.大數取模(乘法逆元和費馬小定理) 4.菲波拉契數列
1.組合數的求法
組合數,爲了方便也寫作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.快速冪
假設我們要計算,記作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函數,也是快速冪實現,並且帶有取模的參數。
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/