[英]Calculate the number of fixed-length strings that contain a given word exactly once
例如,給定固定長度 N=10 和單詞 W="radar"。 我們也知道我們有 26 個字符。
然后我們可以“輕松”計算字符串的數量:
radar _ _ _ _ _ -> 26^5-26-1(因為 radarradar 和 radaradar_ 不正常)
_ 雷達 _ _ _ _ -> 26^5-26
_ _ 雷達 _ _ _ -> 26^5
_ _ _ 雷達 _ _ -> 26^5
_ _ _ _ 雷達 _ -> 26^5-26
_ _ _ _ _ 雷達 -> 26^5-26-1
哪個是 71288150。有沒有更好的解決方案來計算這個? 甚至,如果 N 可以很大。
這個答案很大程度上是基於 軟件工程堆棧線程問一個相關問題,它本身是基於 KMP 算法的。
這個想法是構建一個確定性有限自動機 ( DFA ),其轉換是小寫字母,可以編碼匹配模式零次、一次或 2 次以上的屬性。 例如,對於 pattern = 'radar',我們的模式長度n
為5
,因此我們的DFA中總共有n + n + 1
個狀態。 將這些視為 3 行狀態會很有幫助:
0
行,第i
列是 state 對應於0
個完全匹配項,最后i
個字母來自我們當前的部分匹配項。1
行,第i
列是 state 對應於1
完全匹配,最后i
個字母來自我們當前的部分匹配。2
行有一個無法轉義的 state:我們已經看到至少2
完整匹配項。 我們的“行”只會增加。 在代碼中,實際上只有一個包含2n+1
個狀態的平面列表,但這只是一個實現細節。
顯示了“雷達”的 DFA 圖的部分繪圖:幾乎所有邊都未繪制,因為每個 state 都有26
向外轉換。 通常最多兩個轉換不會導致返回行開頭的“不匹配”狀態(此處為0
和5
),但我也不能包括一些后邊,其中 .networkx 會將它們繪制重疊。
為了得到答案,我們需要在看到length = 10
字母后計算每個可能的 state(從 0 開始)出現的頻率——好的狀態是n, n+1... 2n-1
,正好對應一場完整的比賽。 要生成轉換矩陣,我們可以使用 KMP 算法中的前綴 function,幾乎不做任何修改。 第一行DFA的定義如下:
For state 0 <= i < n,
DFA[i][letter] = Length of longest suffix of (pattern[:i]+letter)
that is also a prefix of pattern
需要注意的是,state n-1
中的完全匹配會將我們移動到第 1 行。狀態n <= i < 2n
的定義是相同的,只是每個 state 向上移動n
。
使用 numpy,我們可以相當快地使用矩陣冪得到答案:我們需要將大約2nx2n
的矩陣提高到我們固定length
的冪。 如果length
太大,一旦答案變得太大,您將需要使用 Python 的大整數或取模以避免溢出。 但是,可以使用朴素矩陣乘法對時間復雜度為O(n^3 * log(length))
的重復平方進行矩陣求冪。
def kmp_count(pattern: str, length: int) -> int:
if not pattern.islower():
raise ValueError("Pattern must be lowercase")
n = len(pattern)
if n > length:
return 0
if n == length:
return 1
if n == 1:
return length * pow(25, length - 1)
pattern_chars = [ord(x) - ord('a') for x in pattern]
# Create the DFA
dfa = [[0 for _ in range(26)] for _ in range(2 * n + 1)]
# Final failure state is an absorbing state
dfa[2 * n] = [2 * n for _ in range(26)]
dfa[0][pattern_chars[0]] = 1
restart_state = 0
for i in range(1, n):
dfa[i] = dfa[restart_state][:]
dfa[i][pattern_chars[i]] = i + 1
restart_state = dfa[restart_state][pattern_chars[i]]
dfa[n - 1][pattern_chars[n - 1]] += restart_state
for i in range(n, 2 * n):
dfa[i] = [x + n for x in dfa[i - n]]
# Two or more matches moves us to fail state
dfa[2 * n - 1][pattern_chars[n - 1]] = 2 * n
transitions = np.zeros((2 * n + 1, 2 * n + 1), dtype=np.int64)
for i, x in enumerate(dfa):
for y in x:
transitions[i][y] += 1
final_transitions = np.linalg.matrix_power(transitions, length)
return final_transitions[0, n:2 * n].sum()
print(kmp_count(pattern='radar', length=10))
>>> 71288150
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.