數據結構和算法筆記(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/

 

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