一個自己研究出來的字串匹配演算法-k子串演算法

haolujun發表於2016-09-11

前言

最近工作中需要寫一個演算法,而寫完這個演算法我卻發現了一個很有意思的事情。需要的這個演算法是這樣的:對於A,B兩個字串,找出最多K個公共子串,使得這K個子串長度和最大。百度之沒有這樣的演算法,然後就開始想了一些亂七八糟的想法,一一被自己舉反例推翻了,直到最後找到了正確演算法,我覺得這個思考過程值得記錄一下。

思考過程

錯誤想法1:每次找最長公共子串,找到一個子串後,從A,B兩個字串中刪除這個子串,之後在剩下的串中再找最長公共子串,像這樣找K次。

舉個反例:

A=KABCDELMABCDEFGNFGHIJK

B=KABCDEFGHIJK

K=2

按照這種方式選取結果為:ABCDEFG+HIJK,總長度為7+4=11,但是最優解為:KABCDE+FGHIJK總長度為6+6=12。

錯誤想法2:求A與B的最長公共子序列,之後從子序列中挑取最長的K段。

舉個反例:

A=EFGIJABC

B=ABCEFHIJ

K=1

按照這種方式選取,首先求出最長公共子序列EF-IJ,取出其中最長一段長度為2。而最優解為:ABC,長度為3。

正確解法:動態規劃

上面兩個看似取巧但是不對的想法被推翻後,也能讓我靜下心來進行系統性的思考了,正確解法為動態規劃。動態規劃最重要的事情有三件:找問題的狀態,找轉移方程邊界初始化。找對狀態就相當於成功了一半,找到轉移方程基本問題就算解了,邊界初始化可以忽略不計。找狀態除了靠靈感之外,我最喜歡的方法是分解問題,找到問題的最原子的狀態,之後搭積木般的組合拼裝就OK了。

設dp[i][j][k]表示為以A[i],B[j]為第K個公共子串結尾時,所能得到的最大值。其中A[i]為字串A第i個字元,B[j]為字串B的第j個字元。

在考慮A[i]和B[j]時,如果A[i] = B[j],那麼A[i],B[j]可以單獨組成第K個串,也可以和A[i-1],B[j-1]組合在一起作為第K個串,則轉移方程如下:

dp[i][j][k] = dp[i-1][j-1][k] + 1             (與A[i-1],B[j-1]連在一起)

dp[i][j][k] = max(dp[i'][j'][k-1] + 1)      (A[i],B[j]單獨成為第K個串)

當單獨成串時,需要遍歷所有i',j',如果我們能在計算dp[i][j][k]的時候順便記錄截止到當前選取k個串時的最大值的話,就可以避免遍歷,所以最後的狀態以及轉移方程如下:

dp[i][j][k] = max(dp[i-1][j-1][k] + 1, maxscore[i-1][j-1][k-1] + 1)

maxscore[i][j][k] = max(maxscore[i-1][j][k],maxscore[i][j-1][k], dp[i][j][k])

現在回頭看一下這個演算法,當K=1的時候就是最長公共子串問題,當K=min(length(A), length(B))的時候就是最長公共子序列問題,想想還是挺有意思是的。

結語

對於一個棘手的問題,可能一開始想不到正確的演算法,我們這時可以儘可能的把能想到的解法全都考慮一遍,該推翻的推翻,該證明的證明,在推翻和證明的過程中更深刻的理解這個問題,最終找到正確的答案。
 

相關文章