簡體   English   中英

計算恰好一次包含給定單詞的固定長度字符串的數量

[英]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',我們的模式長度n5 ,因此我們的DFA中總共有n + n + 1個狀態。 將這些視為 3 行狀態會很有幫助:

  • 0行,第i列是 state 對應於0個完全匹配項,最后i個字母來自我們當前的部分匹配項。
  • 1行,第i列是 state 對應於1完全匹配,最后i個字母來自我們當前的部分匹配。
  • 2行有一個無法轉義的 state:我們已經看到至少2完整匹配項。

我們的“行”只會增加。 在代碼中,實際上只有一個包含2n+1個狀態的平面列表,但這只是一個實現細節。

顯示了“雷達”的 DFA 圖的部分繪圖:幾乎所有邊都未繪制,因為每個 state 都有26向外轉換。 通常最多兩個轉換不會導致返回行開頭的“不匹配”狀態(此處為05 ),但我也不能包括一些后邊,其中 .networkx 會將它們繪制重疊。

雷達的 DFA

為了得到答案,我們需要在看到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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM