演算法的套路
A. chan綆楁硶鏄浠涔堟剰鎬濓紵
浠涔堟槸chan綆楁硶錛熺畝鍗曟潵璇達紝瀹冩槸涓縐嶈В鍐沖嚫鍖呴棶棰樼殑綆楁硶銆傚嚫鍖呴棶棰樻槸璁$畻涓緇勭偣鐨勬渶灝忓嚫鍖呯殑闂棰橈紝鑰宑han綆楁硶鍒欐槸鍦ㄨ$畻鍑稿寘鏃訛紝浼樺寲浼犵粺鐨凣raham綆楁硶鍜孞arvis綆楁硶鐨勬晥鐜囧拰鎴愭湰銆傞氳繃灝嗙偣鐨勯泦鍚堝垎鎴愬皬鐨勫瓙闆嗭紝騫跺埄鐢ㄩ儴鍒嗙粨鏋滄潵璁$畻鍑稿寘錛宑han綆楁硶鑳藉湪鏇寸煭鐨勬椂闂村唴瀹屾垚榪欓」浠誨姟銆
chan綆楁硶鐨勫師鐞嗘槸浠涔堬紵
chan綆楁硶鏄濡備綍璇嗗埆鍑稿寘鐨勶紵瀹冨熀浜庣敱涓や釜瀛愰棶棰樼粍鎴愮殑濂楄礬錛氭壘鍒扮偣闆嗙殑鏈灝忓嚫鍖咃紝騫跺皢鐐瑰垝鍒嗕負灝忛儴鍒嗐傞氳繃鎵懼埌姣忎釜瀛愰泦鐨勭Щ浜ょ偣錛宑han綆楁硶鑳藉熻$畻鍑烘暣涓鐐歸泦鐨勫嚫鍖呫傞殢鐫闂棰樼殑鍙樺緱鏇村姞澶嶆潅錛宑han綆楁硶鐨勮繍琛岄熷害浠嶇劧闈炲父楂橈紝榪欐槸鐢卞叾鍒涙柊鎬濈淮銆佸叿鏈夎嚜閫傚簲瑙e喅鏂規堝拰鍒嗚屾不涔嬬殑鏂規硶鎵鍐沖畾鐨勩
chan綆楁硶鍦ㄨ稿氶嗗煙閮芥湁騫挎硾鐨勫簲鐢ㄣ備緥濡傦紝瀹冨彲鐢ㄤ簬CAD杞浠朵腑鐨勫嚑浣曟暟鎹搴擄紝涔熷彲鐢ㄤ簬鍏ㄦ伅鎴愬儚鍜岀嚎鎬ц勫垝鐨勬ц兘浼樺寲銆俢han綆楁硶榪樻槸鍏朵粬綆楁硶鐨勫熀紜錛屽備駭鐢2D鍗婇忔槑瀵硅薄錛堝傞紶鏍囨寚閽堬級錛屼互鍙婃父鎴忓紑鍙戜腑鐨勭版挒媯嫻嬨傛棤璁虹敤浜庡摢縐嶅簲鐢錛宑han綆楁硶閮芥槸鍏堣繘涓旈珮鏁堢殑瑙e喅鏂規堬紝鍙浠ヤ負璁$畻鏈虹戝﹀拰紱繪暎鏁板﹂嗗煙鐨勫墠娌跨爺絀跺仛鍑洪噸瑕佽礎鐚銆
B. 我出三百,你出三百,你再給我二百,剩下的你拿走
我放300,你放300,你給我200,剩下的是400;
400-300=100;
因此你得到了100;
計算的定義
計算的定義有許多種使用方式,有相當精確的定義,例如使用各種演算法進行的「算術」,也有較為抽象的定義,例如在一場競爭中「策略的計算」或是「計算」兩人之間關系的成功機率。
將7乘以8(7x8)就是一種簡單的算術。數學中的計算有加,減,乘,除,乘方,開方等。其中加減乘除被稱為四則運算。
利用布萊克-舒爾斯定價模型(Black-Scholes Model)來算出財務評估中的公平價格(fair price)就是一種復雜的算術。
從投票意向計算評估出的選舉結果(民意調查)也包含了某種算術,但是提供的結果是「各種可能性的范圍」而不是單一的正確答案。
決定如何在人與人之間建立關系的方式也是一種計算的結果,但是這種計算難以精確、不可預測,甚至無法清楚定義。這種可能性無限的計算定義,和以上提到的數學算術大不相同。
英文中的計算為「Calculation」,來自拉丁文中的「Calculus」,指的是算盤上用來計算的小石頭。
C. 演算法題套路總結(三)——動態規劃
前兩篇我總結了鏈表和二分查找題目的一些套路,這篇文章來講講動態規劃。動態規劃從我高中開始參加NOIP起就一直是令我比較害怕的題型,除了能一眼看出來轉移方程的題目,大部分動態規劃都不太會做。加上後來ACM更為令人頭禿的動態規劃,很多題解看了之後,我根本就不相信自己能夠想出來這種解法,看著大佬們談笑間還加一些常數優化,一度懷疑自己的智商。以前一直覺得動態規劃是給大佬准備的,所以刻意地沒有去攻克它,主要也是沒有信心。但是後來慢慢的,我再做LC的時候,發現很多DP的題目我自己慢慢能夠推出轉移方程了,而且似乎也沒那麼難。我一直在思考,到底是我變強了,還是因為LC的題目相比ACM或者NOI太簡單了。其實主要還是後者,但是同時我也發現,動態規劃其實是有套路的,我以前方法不對,總結太少。
主要就是,站在出題人的角度,他幾乎不太可能完全憑空想出一個新的DP模型,因為動態規劃畢竟要滿足:
因此,能夠利用DP來解決的問題實際上是有限的,大部分題目都是針對現有的模型的一些變種,改改題目描述,或者加點限制條件。所以要想攻克DP題目,最根本的就是要充分理解幾個常見的DP模型。而要充分理解常見經典DP模型,就需要通過大量的做題和總結,而且二者不可偏廢。通過做題進行思考和量的積累,通過總結加深理解和融會貫通進而完成質的提升。
動態規劃是求解一個最優化問題,而最核心的思想就是:
解一道DP題目,先問自己幾個問題:
當然以上內容看起來比較抽象,雖然它深刻地揭露了動態規劃的本質,但是如果臨場要去想明白這些問題,還是有些難度。如果只是針對比賽和面試,就像前面說的,DP題型是有限的。只要刷的題目足夠多,總結出幾個經典模型,剩下的都是些變種+優化而已。
一般來說,動態規劃可以分成4個大類:
線性DP就是階段非常線性直觀的模型,比如:最長(上升|下降)序列,最長公共子序列(LCS)等,也有一些簡單的遞推,甚至都算不上是 經典模型 。
最長上升序列是一個非常經典的線性模型。說它是個模型,是因為它是一類題的代表,很多題目都只是換個說法,或者要求在這基礎上進一步優化而已。最長上升序列最基礎的轉移方程就是 f[i] = max{f[j]}+1 (a[i] > a[j]) , f[i] 表示一定要以 a[i] 結尾的序列,最長長度是多少。很顯然就是在前面找到一個最大的 f[j] 同時滿足 a[j]<a[i] 。因此是 N^2 的時間復雜度和N的空間復雜度。這種方法是最樸素直觀的,一定要理解。它非常簡單,因此很少有題目直接能夠這么做。大部分相關題目需要進一步優化,也就是有名的單調隊列優化,能夠把復雜度優化到nlogn。
說單調隊列優化之前必須明白一個貪心策略。因為要求的是最長上升序列,那麼很顯然長度為k的上升序列的最大值(最後一個數)越小越好,這樣後面的數才有更大的概率比它大。如果我們記錄下來不同長度的上升序列的最後一個數能達到的最小值,那麼對於後續每個數t,它要麼能放到某個長度為y的序列之後,組成長度為y+1的上升序列,要麼放到某個長度為x的序列後面,把長度為x+1的序列的最大值替換成t。同時我們可以發現,如果x<y,那麼長度為x序列的最後一個數一定比長度為y的序列最後一個數小。因此這個上升序列我們可以用一個數組來維護(所謂的單調隊列),數組下標就代表序列長度。 opt[i]=t 表示長度為i的上升序列最後一個數最小是t。那麼當我們在面對後續某個數x時,可以對單調隊列opt進行二分,把它插到對應的位置。因此總體復雜度就是NlogN。
相關題目比如:
但是你可以發現,其實這個題型其實變種很有限,吃透了也就那麼回事。所以一定要總結。
最長公共子序列也是線性DP中的一種比較常見的模型。說它是一種「模型」其實有點拔高了,其實它就是一類比較常見的題目。很多題目都是在LCS的基礎上進行簡單的擴展,或者僅僅就是換一個說法而已。
求兩個數組的最長公共子序列,最直觀地做法就是:設f[i][j]表示S[..i]和T[..j]的最長公共子序列,則有:
這個轉移方程也非常好理解,時間復雜度是 N^2 ,空間復雜度也是 N^2 。不過仔細觀察你可以發現,當我們計算第i行時只與i-1和i行有關。因此我們可以利用01滾動來優化空間復雜度為2N。
相關題目:
線性DP除了上述的兩種常見題型,還有很多別的類型,包括背包。我們要努力去嘗試理解這些題目的異同,它們的轉移方程,以及思路,可能的變化,這樣才能更好的應對未知的題目。以下是一些我總結的題型:
最終結果就是max(0, f[n][2]+f[n][4])。
不過實際上你可以發現,由於各個狀態只和前一維有關,且只能由固定的一個狀態轉移過來,因此我們可以省掉一維,只用4個變數來存儲:
剩下的,同123題類似,由於最多進行k次交易,那麼一天就有2k個狀態:第1次買/賣……第k次買/賣,結合123題的優化,我們只需要2k個變數就能存儲這些狀態。因此設f[i×2]為第i次買入的最優值,f[i×2+1]為第i次賣出的最優值:
以上都是對一些常見的線性DP的一些小結,實際上線性DP還有一個重要的題型就是背包。關於背包,有很多相關的講解,我這里就不多說了,推薦大家看看 背包九講 。下一章依然是DP專題,我講總結一些區間DP的題型。大部分區間DP都是hard級的,對於希望提高自己水平的人來說,需要投入更多精力去理解。