【推薦算法工程師技術棧系列】數據結構與算法--數學建模與解題方法


常見符號

復雜度函數

  1. \(O\) 符號:當且僅當存在正實數 \(M\) 和實數 \(x_0\) ,使得 \(\forall x\geq x_0,\ |f(x)|\leq M|g(x)|\) ,我們就可以認為, \(f(x)=O(g(x))\)
  2. \(\Omega\) 符號:當且僅當存在正實數 \(M\) 和實數 \(x_0\) ,使得 \(\forall x\geq x_0,\ f(x)\geq Mg(x)\) ,我們就可以認為, \(f(x)=\Omega (g(x))\) . 大 \(O\) 與大 \(\Omega\) 恰好相反,即 \(f(x)=O(g(x))\Leftrightarrow g(x)=\Omega(f(x))\)
  3. \(\Theta\) 符號:大 \(\Theta\) 符號是大 \(\text{O}\) 和大 \(\Omega\) 的結合,即 \(f(x)=O(g(x))\wedge f(x)=\Omega(g(x))\ \Rightarrow f(x)=\Theta(g(x))\)

整除/同余理論常見符號

  1. 整除符號: \(x\mid y\) ,表示 \(x\) 整除 \(y\) ,即 \(x\)\(y\) 的因數。
  2. 取模符號: \(x\bmod y\) ,表示 \(x\) 除以 \(y\) 得到的余數。
  3. 互質符號: \(x\perp y\) ,表示 \(x\) , \(y\) 互質。
  4. 最大公約數: \(\gcd(x,y)\) ,在無混淆意義的時侯可以寫作 \((x,y)\)
  5. 最小公倍數: \(\operatorname{lcm}(x,y)\) ,在無混淆意義的時侯可以寫作 \([x,y]\)

數論函數常見符號

求和符號: \(\sum\) 符號,表示滿足特定條件的數的和。舉幾個例子:

  • \(\sum_{i=1}^n i\) 表示 \(1+2+\dotsb+n\) 的和。其中 \(i\) 是一個變量,在求和符號的意義下 \(i\) 通常是 正整數或者非負整數 (除非特殊說明)。這個式子的含義可以理解為, \(i\)\(1\) 循環到 \(n\) ,所有 \(i\) 的和。這個式子用代碼的形式很容易表達。當然,學過簡單的組合數學的同學都知道 \(\sum_{i=1}^n i=\frac{n(n+1)}{2}\)
  • \(\sum_{S\subseteq T}|S|\) 表示所有被 \(T\) 包含的集合的大小的和。
  • \(\sum_{p\le n,p\perp n}1\) 表示的是 \(n\) 以內有多少個與 \(n\) 互質的數,即 \(\varphi(n)\)\(\varphi\) 是歐拉函數。

求積符號: \(\prod\) 符號,表示滿足特定條件的數的積。舉幾個例子:

  • \(\prod_{i=1}^ni\) 表示 \(n\) 的階乘,即 \(n!\) 。在組合數學常見符號中會講到。
  • \(\prod_{i=1}^na_i\) 表示 \(a_1\times a_2\times a_3\times \dotsb\times a_n\)
  • \(\prod_{x|d}x\) 表示 \(d\) 的所有因數的乘積。

在行間公式中,求和符號與求積符號的上下條件會放到符號的上面和下面,這一點要注意。

其他常見符號

  1. 階乘符號 \(!\)\(n!\) 表示 \(1\times 2\times 3\times \dotsb \times n\)
  2. 向下取整符號: \(\lfloor x\rfloor\) ,表示小於等於 \(x\) 的最大的整數。常用於分數,比如分數的向下取整 \(\left\lfloor\frac{x}{y}\right\rfloor\)
  3. 向上取整符號: \(\lceil x\rceil\) ,與向下取整符號相對,表示大於等於 \(x\) 的最小的整數。

最優化初步

優化問題及其性質

最優化,是應用數學的一個分支,主要研究以下形式的問題:
給定一個函數\(f : A -> R\),尋找一個元素\(x^0 \in A\)使得對於所有A中的x,\(f(x^0)\leq f(x)\)(最小化);或者\(f(x^0)\geq f(x)\)(最大化)。
這類定式有時還稱為“數學規划”(譬如,線性規划)。許多現實和理論問題都可以建模成這樣的一般性框架。
典型的,A一般為歐幾里得空間\(R^n\)中的子集,通常由一個A必須滿足的約束等式或者不等式來規定。 A的元素被稱為是可行解。函數f被稱為目標函數,或者代價函數。一個最小化(或者最大化)目標函數的可行解被稱為最優解。
一般情況下,會存在若干個局部的極小值或者極大值。局部極小值\(x^*\)定義為對於一些\(\delta >0\),以及所有的x滿足\(||f(x) - f(x^*)|| \leq \delta\);
公式\(f(x^*) \leq f(x)\) 成立。這就是說,在$f(x^*) $周圍的一些閉球上,所有的函數值都大於或者等於在該點的函數值。一般的,求局部極小值是容易的,但是要確保其為全域性的最小值,則需要一些附加性的條件,例如,該函數必須是凸函數。

主要分支

  • 線性規划:當目標函數f是線性函數而且集合A是由線性等式函數和線性不等式函數來確定的, 我們稱這一類問題為線性規划
  • 整數規划:當線性規划問題的部分或所有的變量局限於整數值時, 我們稱這一類問題為整數規划問題
  • 二次規划:目標函數是二次函數,而且集合A必須是由線性等式函數和線性不等式函數來確定的。
  • 分數規划:研究的是如何優化兩個非線性函數的比例。
  • 非線性規划:研究的是目標函數或是限制函數中含有非線性函數的問題。
  • 隨機規划:研究的是某些變量是隨機變量的問題。
  • 動態規划:研究的是最優策略基於將問題分解成若干個較小的子問題的優化問題。
  • 組合最優化:研究的是可行解是離散或是可轉化為離散的問題。
  • 無限維最優化:研究的是可行解的集合是無限維空間的子集的問題,一個無限維空間的例子是函數空間。

無約束優化問題

對於無約束的優化問題, 如果函數是二次可微的話,可以通過找到目標函數梯度為0(也就是拐點)的那些點來解決此優化問題。我們需要用黑塞矩陣來確定此點的類型。如果黑塞矩陣是正定的話,該點是一個局部最小解, 如果是負定的話,該點是一個局部最大解,如果黑塞矩陣是不定的話,該點是某種鞍點
要找到那些拐點,我們可以通過猜測一個初始點,然后用比如以下的迭代的方法來找到。

如果目標函數在我們所關心的區域中是凸函數的話,那么任何局部最小解也是全局最優解。現在已經有穩定,快速的數值計算方法來求二次可微地凸函數的最小值。

有約束優化問題

有約束條件的約束問題常常可以通過拉格朗日乘數轉化為非約束問題。
其他一些流行的方法有:

概率統計

  • 概率論(英語:Probability theory)是集中研究概率及隨機現象的數學分支,是研究隨機性或不確定性等現象的數學。概率論主要研究對象為隨機事件、隨機變量以及隨機過程

  • 統計學是在數據分析的基礎上,研究測定、收集、整理、歸納和分析反映數據數據,以便給出正確消息的科學。譬如自一組數據中,可以摘要並且描述這份數據的集中和離散情形,這個用法稱作為描述統計學。另外,觀察者以數據的形態,創建出一個用以解釋其隨機性和不確定性的數學模型,以之來推論研究中的步驟及總體,這種用法被稱做推論統計學。這兩種用法都可以被稱作為應用統計學數理統計學則是討論背后的理論基礎的學科。
    描述統計學處理有關敘述的問題:是否可以摘要的說明數據的情形,不論是以數學或是圖片表現,以用來代表總體的性質?基礎的數學描述包括了平均數和標准差等。圖像的摘要則包含了許多種的表和圖。主要是就說明數據的集中和離散情形。
    推論統計學被用來將數據中的數據模型化,計算它的概率並且做出對於總體的推論。這個推論可能以對/錯問題的答案所呈現(假設檢定),對於數字特征量的估計(估計),對於未來觀察的預測,關系性的預測(相關性),或是將關系模型化(回歸)。其他的模型化技術包括方差分析,時間序列,以及數據挖掘

樣本及抽樣分布

抽樣(Sampling)是一種推論統計方法,它是指從目標總體(Population,或稱為母體)中抽取一部分個體作為樣本(Sample),通過觀察樣本的某一或某些屬性,依據所獲得的數據對總體的數量
特征得出具有一定可靠性的估計判斷,從而達到對總體的認識。常用的抽樣方法簡單隨機抽樣,分層抽樣,系統抽樣和整群抽樣。

參數:描述總體或概率分布的數量值,是未知的;如:正態分布的均值, 正態分布的標准差等

樣本統計量(statistic):樣本數據特征值的數量描述;如:樣本均值, 樣本比例,樣本方差等

抽樣分布:在重復選取容量為n的樣本時,由該統計量的所有可能取值形成的相對頻數分布。三大抽樣分布:卡方分布,F分布,和t分布

概率分布

常見概率分布圖表總結
Zipf分布以及對數正態分布
概率論雜記

參數估計

假設檢驗

(1) 假設檢驗問題: 在總體的分布函數完全未知,或只知其形式但不知其參數的情況,提出某些關於總體分布函數或對於其參數的假設,然后抽取樣本,構造合適的統計量,在根據樣本對所提的假設作出是接受還是拒絕的決策,這樣的問題稱為假設檢驗問題。
(2) 檢驗法: 借助於樣本值來判斷接受假設還是拒絕假設的法則,稱為檢驗法。
(3) 原假設與備擇假設: 稱需要着重觀察的假設為原假設(試驗者想收集證據予以反對的假設 ,又稱“零假設”),原假設常記為H0。與原假設相對立的假設稱為備擇假設或者對立假設,備擇假設通常記為H1。
(4) 檢驗統計量: 如果根據某一統計量的觀測值來決定接受H0還是拒絕H0,這一統計量稱為檢驗統計量。
(5) 拒絕域和臨界點: 當檢驗統計量的觀測值落在某個區域時就拒絕H0,這一區域稱為拒絕域,拒絕域的邊界點稱為臨界點。
(6) 顯著性水平: 在做檢驗時要求犯第一類錯誤的概率不大於α,α稱為檢驗的顯著性水平。
(7) 顯著性檢驗: 對於給定的樣本容量,只控制犯第一種錯誤的概率,而不考慮犯第二種錯誤的概率的檢驗法,稱為顯著性檢驗。

邊界值
臨界值是在原假設下,檢驗統計量在分布圖上的點,這些點定義一組要求否定原假設的值。這組值稱為臨界或否定區域。通常,單側檢驗有一個臨界值,雙側檢驗有兩個臨界值。在臨界值處,當原假設為真時,檢驗統計量在檢驗的否定區域中有值的概率等於顯著性水平(用 α 或 alpha 表示)

P值
即概率,反映某一事件發生的可能性大小。統計學根據顯著性檢驗方法所得到的P值,一般以P < 0.05 為有統計學差異, P<0.01 為有顯著統計學差異,P<0.001為有極其顯著的統計學差異
顯著性水平p是指在原假設為真的條件下,樣本數據拒絕原假設這樣一個事件發生的概率。例如,我們根據某次假設檢驗的樣本數據計算得出顯著性水平 p = 0.04;這個值意味着如果原假設為真,我們通過抽樣得到這樣一個樣本數據的可能性只有 4%。
那么,0.04 這個概率或者說顯著性水平到底是大還是小,夠還是不夠用來拒絕原假設呢?這就需要把 p 和我們采用的第 I 類錯誤的小概率標准 α 來比較確定。假設檢驗的決策規則:
若 p ≤ α,那么拒絕原假設;
若 p > α,那么不能拒絕原假設。

如果 α 取 0.05 而 p = 0.04,說明如果原假設為真,則此次試驗發生了小概率事件。根據小概率事件不會發生的判斷依據,我們可以反證認為原假設不成立。

置信區間
置信區間是從樣本統計量派生的值范圍,可能包含未知總體參數的值。由於置信區間具有隨機性,因此來自特定總體的兩個樣本將不可能生成相同的置信區間。但是,如果您將樣本重復多次,則在所生成的置信區間中有特定百分比的置信區間將包含未知總體參數。
如果置信區間同為正或同為負,說明試驗結果是統計顯著的。如果置信區間為一正一負,說明試驗結果是非統計顯著的。

方差分析及回歸分析

回歸分析(regression analysis) 是確定兩種或兩種以上變量間相互依賴的定量關系的一種統計分析方法。運用十分廣泛,回歸分析按照涉及的變量的多少,分為一元回歸和多元回歸分析;按照因變量的多少,可分為簡單回歸分析和多重回歸分析;按照自變量和因變量之間的關系類型,可分為線性回歸分析和非線性回歸分析。

方差分析 (Analysis of Variance,簡稱 ANOVA),又稱 “變異數分析”,用於兩個及兩個以上樣本均數差別的顯著性檢驗。方差分析是從觀測變量的方差入手,研究諸多控制變量中哪些變量是對觀測變量有顯著影響的變量。其分為單因素方差分析,無交互作用的雙因素方差分析,有交互作用的雙因素方差分析,多因素正交表設計及方差分析。

方差分析考察的是自變量對因變量的影響是否顯著,而回歸分析考察的是自變量與因變量之間的可以用數學表達式來表示的線性相關關系。

隨機過程

在概率論概念中,隨機過程是隨機變量的集合。若一隨機系統的樣本點是隨機函數,則稱此函數為樣本函數,這一隨機系統全部樣本函數的集合是一個隨機過程。實際應用中,樣本函數的一般定義在時間域或者空間域。隨機過程的實例如股票和匯率的波動、語音信號、視頻信號、體溫的變化,隨機運動如布朗運動、隨機徘徊等等

隨機過程的一些例子:伯努利過程,正弦波過程(\(x(t) = v\sin (\omega t + \phi)\))

隨機過程是一個統稱,根據T是否連續可以分為離散隨機過程和連續隨機過程。離散的隨機過程也叫作隨機序列或者時間序列

隨機算法

隨機化算法(randomized algorithm),是這樣一種算法,在算法中使用了隨機函數,且隨機函數的返回值直接或者間接的影響了算法的執行流程或執行結果。就是將算法的某一步或某幾步置於運氣的控制之下,即該算法在運行的過程中的某一步或某幾步涉及一個隨機決策,或者說其中的一個決策依賴於某種隨機事件。隨機算法(概率算法)大致分為四類:數值概率算法,蒙特卡羅(Monte Carlo)算法,拉斯維加斯(Las Vegas)算法和舍伍德(Sherwood)算法

數值概率算法

數值概率算法常用於數值問題的求解。這類算法所得到的往往是近似解。而且近似解的精度隨計算時間的增加不斷提高。在許多情況下,要計算出問題的精確解是不可能或沒有必要的,因此用數值概率算法可得到相當滿意的解。

蒙特卡羅(Monte Carlo)算法

蒙特卡羅(Monte Carlo)算法用於求問題的准確解。用蒙特卡羅算法能求得問題的一個解,但這個解未必是正確的。求得正確解的概率依賴於算法所用的時間。算法所用的時間越多,得到正確解的概率就越高。蒙特卡羅算法的主要缺點就在於此。一般情況下,無法有效判斷得到的解是否肯定正確。
蒙特卡羅方法的解題過程可以歸結為三個主要步驟:
1)構造或描述概率過程;
2)實現從已知概率分布抽樣:使用偽隨機數序列模擬概率分布采樣
3)建立各種估計量。

蒙特卡羅(Monte Carlo)算法實例

拉斯維加斯(Las Vegas)算法

拉斯維加斯(Las Vegas)算法不會得到不正確的解,一旦用拉斯維加斯算法找到一個解,那么這個解肯定是正確的。但是有時候用拉斯維加斯算法可能找不到解。與蒙特卡羅算法類似。拉斯維加斯算法得到正確解的概率隨着它用的計算時間的增加而提高。對於所求解問題的任一實例,用同一拉斯維加斯算法反復對該實例求解足夠多次,可使求解失效的概率任意小。

舍伍德(Sherwood)算法

舍伍德(Sherwood)算法總能求得問題的一個解,且所求得的解總是正確的。當一個確定性算法在最壞情況下的計算復雜性與其在平均情況下的計算復雜性有較大差別時,可以在這個確定算法中引入隨機性將它改造成一個舍伍德算法,消除或減少問題的好壞實例間的這種差別。舍伍德算法精髓不是避免算法的最壞情況行為,而是設法消除這種最壞行為與特定實例之間的關聯性。這意味着不存在壞的輸入,只有壞的隨機數。

數論

整除及其性質

最大公約數

最大公約數即為 Greatest Common Divisor,常縮寫為 gcd。
在 素數 一節中,我們已經介紹了約數的概念。
一組數的公約數,是指同時是這組數中每一個數的約數的數。而最大公約數,則是指所有公約數里面最大的一個。

歐幾里德算法

如果我們已知兩個數 \(a\)\(b\) ,如何求出二者的最大公約數呢?
不妨設 \(a > b\)
我們發現如果 \(b\)\(a\) 的約數,那么 \(b\) 就是二者的最大公約數。 下面討論不能整除的情況,即 \(a = b \times q + r\) ,其中 \(r < b\)
我們通過證明可以得到 \(\gcd(a,b)=\gcd(b,a \bmod b)\) ,過程如下:
\(a=bk+c\) ,顯然有 \(c=a \bmod b\) 。設 \(d|a\\\ d|b\)(\(\mid\)為整除符號) ,則 \(c=a-bk\) \(\frac{c}{d}=\frac{a}{d}-\frac{b}{d}k\) 由右邊的式子可知 \(\frac{c}{d}\) 為整數,即 \(d|c\) 所以對於 \(a,b\) 的公約數,它也會是 \(a \bmod b\) 的公約數。
反過來也需要證明
\(d|b\ \ \ d|(a \bmod b)\) ,我們還是可以像之前一樣得到以下式子 \(\frac{a\bmod b}{d}=\frac{a}{d}-\frac{b}{d}k\) \(\frac{a\bmod b}{d}+\frac{b}{d}k=\frac{a}{d}\) 因為左邊式子顯然為整數,所以 \(\frac{a}{d}\) 也為整數,即 \(d|a\) ,所以 \(b,a\bmod b\) 的公約數也是 \(a,b\) 的公約數。
既然兩式公約數都是相同的,那么最大公約數也會相同。
所以得到式子 \(\gcd(a,b)=\gcd(b,a\bmod b)\)
既然得到了 \(\gcd(a, b) = \gcd(b, r)\) ,這里兩個數的大小是不會增大的,那么我們也就得到了關於兩個數的最大公約數的一個遞歸求法。

int gcd(int a, int b) {
  if (b == 0) return a;
  return gcd(b, a % b);
}

非遞歸寫法

public int GCD(int a, int b){
    while(b != 0 ){
        int temp = b;
        b = a%b;
        a = temp;
    }
    return a;
}

遞歸至 b0 (即上一步的 a%b0 ) 的情況再返回值即可。
上述算法被稱作歐幾里德算法(Euclidean algorithm)。
如果兩個數 \(a\)\(b\) 滿足 \(\gcd(a, b) = 1\) ,我們稱 \(a\)\(b\) 互質。

歐拉函數

歐拉函數(Euler's totient function),即 \(\varphi(n)\) ,表示的是小於等於 \(n\)\(n\) 互質的數的個數
比如說 \(\varphi(1) = 1\)
當 n 是質數的時候,顯然有 \(\varphi(n) = n - 1\)
利用唯一分解定理,我們可以把一個整數唯一地分解為質數冪次的乘積,
\(n = p_1^{k_1}p_2^{k_2} \cdots p_s^{k_s}\) ,其中 \(p_i\) 是質數,那么定義 \(\varphi(n) = n \times \prod_{i = 1}^s{\frac{p_i - 1}{p_i}}\)

歐拉函數的一些神奇性質

  • 歐拉函數是積性函數。
    積性是什么意思呢?如果有 \(\gcd(a, b) = 1\) ,那么 \(\varphi(a \times b) = \varphi(a) \times \varphi(b)\)
    特別地,當 \(n\) 是奇數時 \(\varphi(2n) = \varphi(n)\)

  • \(n = \sum_{d | n}{\varphi(d)}\)
    利用 莫比烏斯反演 相關知識可以得出。
    也可以這樣考慮:如果 \(\gcd(k, n) = d\) ,那么 \(\gcd(\frac{k}{d},\frac{n}{d}) = 1\) 。( \(k < n\)
    如果我們設 \(f(x)\) 表示 \(\gcd(k, n) = x\) 的數的個數,那么 \(n = \sum_{i = 1}^n{f(i)}\)
    根據上面的證明,我們發現, \(f(x) = \varphi(\frac{n}{x})\) ,從而 \(n = \sum_{d | n}\varphi(\frac{n}{d})\) 。注意到約數 \(d\)\(\frac{n}{d}\) 具有對稱性,所以上式化為 \(n = \sum_{d | n}\varphi(d)\)

  • \(n = p^k\) ,其中 \(p\) 是質數,那么 \(\varphi(n) = p^k - p^{k - 1}\) 。 (根據定義可知)

如何求歐拉函數值
如果只要求一個數的歐拉函數值,那么直接根據定義質因數分解的同時求就好了。

int ol(int x) {
  int res=x;
  for(int i=2;i*i<=x;i++) {
      if(x%i==0) {
         res=res-res/i;
         while(x%i==0)
             x/=i;
      }
  }
  if(x>1) res=res-res/x;
  return res;
}

如果是多個數的歐拉函數值,可以利用后面會提到的線性篩法來求得。 詳見:篩法求歐拉函數

歐拉定理
與歐拉函數緊密相關的一個定理就是歐拉定理。其描述如下:
\(\gcd(a, m) = 1\) ,則 \(a^{\varphi(m)} \equiv 1 \pmod{m}\)

擴展歐拉定理(降冪公式)
當然也有擴展歐拉定理

\[a^b\equiv a^{b\bmod\varphi(p)+\varphi(p)} \pmod p, \gcd(a,p) \ne 1,b \ge \varphi(p) \]

證明和習題詳見歐拉定理

同余與同余方程

裴蜀定理

裴蜀定理,又稱貝祖定理(Bézout's lemma/Bézout's identity);若a,b是整數,且gcd(a,b)=d,那么對於任意的整數x,y,ax+by都一定是d的倍數,特別地,一定存在整數x,y,使ax+by=d成立
證明見裴蜀定理

問題:有兩個容量分別為 x升 和 y升 的水壺以及無限多的水。請判斷能否通過使用這兩個水壺,從而可以得到恰好 z升 的水?

public boolean canMeasureWater(int x, int y, int z) {
    // 兩個水壺都倒滿也得不到所求
    if(x + y < z) return false;
    if( x == z || y == z || x + y == z ) return true;
    // 裴蜀定理:x,y不全為0時,一定存在整數a,b;使得ax+by % gcd(x,y) = 0;
    return z%GCD(x, y) == 0;
}

public int GCD(int a, int b){
    while(b != 0 ){
        int temp = b;
        b = a%b;
        a = temp;
    }
    return a;
}

快速冪和矩陣快速冪

快速冪,二進制取冪(Binary Exponentiation,也稱平方法),是一個在 \(\Theta(\log n)\) 的時間內計算 \(a^n\) 的小技巧,而暴力的計算需要 \(\Theta(n)\) 的時間。而這個技巧也常常用在非計算的場景,因為它可以應用在任何具有結合律的運算中。其中顯然的是它可以應用於模意義下取冪、矩陣冪等運算。

求$a^n = a^{c_1} * a^{c_2} ... a*{c_k} \(,其中c1,c2,...,ck是b對應二進制表示中為一的權值;時間復雜度O(\)log_2N$)。
將上述過程說得形式化一些,如果把 \(n\) 寫作二進制為 $(n_t n_{t-1} ... n_1 n_0)_2 $ ,那么有:

\[n = n_t2^t + n_{t-1}2^{t-1} + n_{t-2}2^{t-2} + \cdots + n_12^1 + n_02^0 \]

其中 \(n_i\in{0,1}\) 。那么就有

\[\begin{aligned} a^n & = (a^{n_t 2^t + \cdots + n_0 2^0}) = a^{n_0 2^0} \times a^{n_1 2^1}\times \cdots \times a^{n_t2^t} \end{aligned} \]

根據上式我們發現,原問題被我們轉化成了形式相同的子問題的乘積,並且我們可以在常數時間內從 \(2^i\) 項推出 \(2^{i+1}\) 項。

int binPow(int a,int b)
{
    int ans = 1,base = a;
    while(b!=0)
    {
        if(b&1)
            ans *= base;
        base *= base;
        b>>=1;
    }
    return ans;

矩陣形式

public static int[][] multiply(int [][]a,int[][]b){
    int[][]arr=new int[a.length][b[0].length];
    for(int i=0;i<a.length;i++){
        for(int j=0;j<b[0].length;j++){
            for(int k=0;k<a[0].length;k++){
                arr[i][j]+=a[i][k]*b[k][j];
            }
        }
    }
    return arr;
}
// 矩陣快速冪a^n
public static int[][] matrixPower(int[][]a,int n){
    int[][] res=new int[a.length][a[0].length];
    // 初始化為單位矩陣I
    for(int i=0;i<res.length;i++){
        for(int j=0;j<res[0].length;j++){
            if(i==j)
                res[i][j]=1;
            else
                res[i][j]=0;
        }

    }
    while(n!=0){
        if((n&1)==1)
            res=multiply(res,a);
        n>>=1;
        a=multiply(a,a);
    }
    return res;
}

模意義下取模

計算 $x^y \mod M \( 注意:根據[費馬小定理](https://oi-wiki.org/math/fermat/),如果M是一個質數,我們可以計算\)x^{n \mod (m-1)}$來加速算法過程。

int pow_mod(long x,int y,int M) {
    long res=1;
    while(y>0) {
        if( y % 2 > 0 ){
            res = res * x % M;
        }
        // 快速冪
        x = x * x % M;
        y /= 2;
    }
    return (int)res;
}

數根

在數學中,數根(又稱位數根數字根Digital root)是自然數的一種性質,換句話說,每個自然數都有一個數根。
數根是將一正整數的各個位數相加(即橫向相加),若加完后的值大於10的話,則繼續將各位數進行橫向相加直到其值小於十為止,或是,將一數字重復做數字和,直到其值小於十為止,則所得的值為該數的數根。例如54817的數根為7,因為5+4+8+1+7=25,25大於10則再加一次,2+5=7,7小於十,則7為54817的數根。

性質

  • 1.兩數之和的數字根等於這兩個數的數字根的和 $ dr_b(a_1+a_2) = dr_b(dr_b(a_1) + dr_b(a_2)) $
  • 2.兩數之差的數字根同余這兩個數的數字根的差模b-1 $ dr_b(a_1-a_2) \equiv (dr_b(a_1) - dr_b(a_2)) \mod b-1 $
  • 3.兩數之積的數字根等於這兩個數的數字根的積 $ dr_b(a_1a_2) = dr_b(dr_b(a_1)*dr_b(a_2)) $

計算公式

見附錄

用途

數根可以計算模運算同余,對於非常大的數字的情況下可以節省很多時間。
數字根可作為一種檢驗計算正確性的方法。例如,兩數字的和的數根等於兩數字分別的數根的和。
另外,數根也可以用來判斷數字的整除性,如果數根能被3或9整除,則原來的數也能被3或9整除。

其他數學問題

約瑟夫環

定義:N個人圍成一圈,第一個人從0開始報數,報M-1的將被殺掉,下一個人接着從0開始報。如此反復,最后剩下一個,求最后的勝利者。
解法:
初始條件\(f(1,M) = 0\)
遞推公式:$f(N,M) = (f(N-1,M) + M) \% N \( 推導: 根據規則,當有人出列之后,下一個位置的人又從0開始報數,則以上列表可調整為以下形式(即以M位置開始,N–1之后再接上0、1、2……,形成環狀): M M+1 M+2 … N-2 N-1 0 1 … M-3 M-2 按上面排列的順序從0開始重新編號,可得到下面的對應關系: M M+1 M+2 … N-2 N-1 0 1 … M-3 M-2 0 1 2 … N-(M+2) N-(M+1) N-M N-(M-1) … N-3 N-2 這里,**假設上一行的數為x,下一行的數為y,則對應關系**為:\)y = (x - M + N) \% N \(,反過來有\) x = (y + M) \% N $

問題中的規模最小時是什么情況?就是只有1個人時(N=1),報數到(M–1)的人出列,這時最后出列的是誰?當然只有編號為0這個人。因此,可設有以下函數:
F(1) = 0
那么,當N=2,報數到(M–1)的人出列,最后出列的人是誰?應該是只有一個人報數時得到的最后出列的序號加上M,因為報到M-1的人已出列,只有2個人,則另一個出列的就是最后出列者,利用公式$ x = (y + M) \% N $,可表示為以下形式:
F(2) = [F(1) + M] % N

斐波那契數列(黃金分割數列)

斐波那契數列(意大利語:Successione di Fibonacci),又譯為菲波拿契數列、菲波那西數列、斐氏數列、黃金分割數列。
在數學上,斐波那契數列是以遞歸的方法來定義:
F(0)=0
F(1)=1
F(n0=F(n-1)+F(n-2)(n≧2)

兔子對的數量就是斐波那契數列。
可通過編程觀察斐波那契數列。分為兩類問題,一種已知數列中的某一項,求序數。第二種是已知序數,求該項的值
可通過遞歸遞推的算法解決此兩個問題。 事實上當n相當巨大的時候,O(n)的遞推/遞歸非常慢……這時候要用到矩陣快速冪這一技巧,可以使遞歸加速到O(logn)。

開普勒發現數列前、后兩項之比1/2 ,2/3 , 3/5 ,5/8 ,8/13 ,13/21 ,21/34 ,...... ,也組成了一個數列,會趨近黃金分割
$ \frac{f_{n+1}}{f_n} \approx \frac{1}{2}(1+\sqrt{5}) = \varphi \approx 1.618 \( 通項公式為\)F_n = \frac{1}{\sqrt{5}} [(\frac{1+\sqrt{5}}{2})^n - (\frac{1-\sqrt{5}}{2})^n] = \frac{\varphi ^n}{\sqrt{5}} - \frac{(1-\varphi)^n}{\sqrt{5}}$

解析解

解析解即公式解。我們有斐波那契數列的通項公式(Binet's Formula):

\[F_n = \frac{\left(\frac{1 + \sqrt{5}}{2}\right)^n - \left(\frac{1 - \sqrt{5}}{2}\right)^n}{\sqrt{5}} \]

這個公式可以很容易地用歸納法證明,當然也可以通過生成函數的概念推導,或者解一個方程得到。
當然你可能發現,這個公式分子的第二項總是小於 \(1\) ,並且它以指數級的速度減小。因此我們可以把這個公式寫成

\[F_n = \left[\frac{\left(\frac{1 + \sqrt{5}}{2}\right)^n}{\sqrt{5}}\right] \]

這里的中括號表示取離它最近的整數。
這兩個公式在計算的時侯要求極高的精確度,因此在實踐中很少用到。

矩陣形式

斐波那契數列的遞推可以用矩陣乘法的形式表達:

\[\begin{bmatrix}F_{n-1} & F_{n} \cr\end{bmatrix} = \begin{bmatrix}F_{n-2} & F_{n-1} \cr\end{bmatrix} \cdot \begin{bmatrix}0 & 1 \cr 1 & 1 \cr\end{bmatrix} \]

\(P = \begin{bmatrix}0 & 1 \cr 1 & 1 \cr\end{bmatrix}\) ,我們得到

\[\begin{bmatrix}F_n & F_{n+1} \cr\end{bmatrix} = \begin{bmatrix}F_0 & F_1 \cr\end{bmatrix} \cdot P^n \]

於是我們可以用矩陣乘法在 \(\Theta(\log n)\) 的時間內計算斐波那契數列。

和為S的連續正數序列

public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
    // 時間復雜度O(n)
    
    //存放結果
    ArrayList<ArrayList<Integer> > result = new ArrayList<>();
    //兩個起點,相當於動態窗口的兩邊,根據其窗口內的值的和來確定窗口的位置和大小
    int plow = 1,phigh = 2;
    while(phigh > plow){
        //由於是連續的,差為1的一個序列,那么求和公式是(a0+an)*n/2
        int cur = (phigh + plow) * (phigh - plow + 1) / 2;
        //相等,那么就將窗口范圍的所有數添加進結果集
        if(cur == sum){
            ArrayList<Integer> list = new ArrayList<>();
            for(int i=plow;i<=phigh;i++){
                list.add(i);
            }
            result.add(list);
            plow++;
            //如果當前窗口內的值之和小於sum,那么右邊窗口右移一下
        }else if(cur < sum){
            phigh++;
        }else{
            //如果當前窗口內的值之和大於sum,那么左邊窗口右移一下
            plow++;
        }
    }
    return result;
}

求眾數

// Boyer-Moore 投票算法
// 假設數組是非空的,並且給定的數組總是存在眾數。
public int majorityElement(int[] nums) {
    if(nums.length == 1){
        return nums[0];
    }
    // 用來保存數組中遍歷到的某個數字
    int major = 0;
	// 如果數字與之前 candidate 保存的數字相同,則 count 加 1
	// 如果數字與之前 candidate 保存的數字不同,則 count 減 1
    int count = 0;
    for(int i:nums){
        if(0 == count){
            major = i;
            count += 1;
        }else if(i == major){
            count ++;
        }else{
            count --;
        }
    }
    return major;
}

楊輝三角形

        1
       1 1
      1 2 1
     1 3 3 1
    1 4 6 4 1

性質

  • 帕斯卡三角以正整數構成,數字左右對稱,每行由1開始逐漸變大,然后變小,回到1。
  • 帕斯卡三角每一行的平方和在楊輝三角出現奇數次。
  • 帕斯卡三角第2的冪行所有數都是奇數。
  • 帕斯卡三角每一行的和是2的冪。
  • 第n行的數字個數為n個。
  • 第n行的第k個數字為組合數\(C_{n-1}^{k-1}\)
  • 第n行數字和為\(2^{n-1}\)
  • 除每行最左側與最右側的數字以外,每個數字等於它的左上方與右上方兩個數字之和(也就是說,第n行第k個數字等於第n-1行的第k-1個數字與第k個數字的和)。這是因為有組合恆等式:\(C_{n+1}^{i+1}=C_{n}^{i}+C_{n}^{i+1}\)。可用此性質寫出整個楊輝三角形。

輸出楊輝三角的第 k 行
楊輝三角n行m列通項公式為:\(C_{n-1}^{m-1} = \frac{(n-1)!}{(m-1)!(n-m)!}\)
組合數又有如下規律:$C_{n}^{m+1} = C_{n}^{m} * \frac{n-m}{m+1} $

// 楊輝三角n行m列通項公式為:$C_{n-1}^{m-1} = \frac{(n-1)!}{(m-1)!(n-m)!}$
// 組合數又有如下規律:$C_{n-1}^{m-1} = C_{n-1}^{m-2} * \frac{n-m}{m-1} $
public List<Integer> getRow(int rowIndex) {
    // note(a-lan-ruo-2): C(4,1)=C(4,0)*4/1,C(4,2)=C(4,1)*3/2,
    //C(4,3)=C(4,2)*2/3,C(4,4)=C(4,3)*1/4:
    //找到規律
    List<Integer> res = new ArrayList<Integer>(rowIndex+1);
    long nk = 1;
    for(int i = 0; i <= rowIndex; i++){
        res.add((int)nk);
        // C_{rowIndex}^{i}到C_{rowIndex}^{i+1}的遞推公式
        nk = nk * (rowIndex - i) / (i + 1);
    }
    return res;
}

丑數

定義1:把只包含質因子2、3和5的數稱作丑數(Ugly Number),習慣上我們把1當做是第一個丑數。
計算:求按從小到大的順序的第N個丑數。
動態規划的記憶存儲化思想

	  public int nthUglyNumber(int n) {
        // 0-6的丑數分別為0-6
        if(n < 7) return n;
        // 因為每次只選擇一個最小的數,所以該用數組和for循環(最大循環次數就是n)
        int[] uglyArray = new int[n+1];
        //i2,i3,i5分別為三個隊列的指針,newNum為從隊列頭選出來的最小數
        int i2=0,i3=0,i5=0,currMin=1;
        uglyArray[0]=1;
        for(int i=1;i<n;i++)
        {
            int m2=uglyArray[i2]*2;
            int m3=uglyArray[i3]*3;
            int m5=uglyArray[i5]*5;
            // 選出三個隊列頭最小的數
            currMin=Math.min(m2,Math.min(m3,m5));
            // 有序加入list中
            uglyArray[i]=currMin;
            // 這三個if有可能進入一個或者多個
            if(currMin==m2)i2++;
            if(currMin==m3)i3++;
            if(currMin==m5)i5++;
        }
        return uglyArray[n-1];
    }

定義2:超級丑數是指其所有質因數都是長度為 k 的質數列表 primes 中的正整數。
計算:查找第 n 個超級丑數。
動態規划的記憶存儲化思想

public int nthSuperUglyNumberI(int n, int[] primes) {
    // 時間復雜度O(NM),m是給定質數列表長度,n是參數第那個丑數
    int[] ugly = new int[n];
    // note(核心數據結構) 下標對應primes每個質數,值對應超級丑數數組的指針
    int[] idx = new int[primes.length];

    ugly[0] = 1;
    for (int i = 1; i < n; i++) {
        // 第一個循環用來計算第i個超級丑數
        ugly[i] = Integer.MAX_VALUE;
        for (int j = 0; j < primes.length; j++){
            ugly[i] = Math.min(ugly[i], primes[j] * ugly[idx[j]]);
        }
        // 第二個循環用來設置
        for (int j = 0; j < primes.length; j++) {
            while (primes[j] * ugly[idx[j]] <= ugly[i]){
                idx[j]+=1;
            }
        }
    }
    return ugly[n - 1];
}

Nim游戲(了解即可)

兩人輪流撿石子,每次最多可撿1到k個,誰最后拿不到石子為輸;Nim游戲是博弈論中最經典的模型之一,它又有着十分簡單的規則和無比優美的結論 Nim游戲是組合游戲(Combinatorial Games)的一種,准確來說,屬於公平組合游戲(以下簡稱ICG);ICG 是指在游戲中,兩個玩家所能進行的移動是完全相同的。

Nim游戲的定義

Nim游戲是經典的公平組合游戲(ICG),對於ICG游戲我們有如下定義:

  • 兩名選手
  • 兩名選手輪流行動,每一次行動可以在有限合法操作集合中選擇一個
  • 游戲的任何一種可能的局面(position),合法操作集合只取決於這個局面本身,不取決於輪到哪名選手操作、以前的任何操作、骰子的點數或者其它因素;局面的改變稱為“移動”(move)
  • 如果輪到某名選手移動,且這個局面的合法的移動集合為空(也就是說此時無法進行移動),則這名選手負

對於第三條,我們有更進一步的定義Position,我們將Position分為兩類:

  • P-position:在當前的局面下,先手必敗
  • N-position:在當前的局面下,先手必勝

它們有如下性質:

附錄


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM