最長重複子序列

1. 題目

給定一個文本作爲輸入,查找其中最長的重複子字符串。

2. 示例

字符串爲:“Ask not what your country can do for you, but what you can do for your country”。

最長的重複字符串爲:“can do for you”。

3. 解法-構造後綴數組

使用稱爲“後綴數組”的簡單數據結構。這個結構是一個字符指針數組,記爲pStr,每個元素都指向字符串中某個位置開始的子字符串。pStr[0]指向整個字符串,pStr[1]指向第二個字符開始的數組後綴。

/* 構造後綴數組 */
vector<const char*> pStr;
const char *str = s.c_str();
for (int i = 0; i < s.size(); i++)
{
    pStr.push_back(str);
    str++;
}

例如:輸入字符串爲:“banana”,則:

  • pStr[0]: banana
  • pStr[1]: anana
  • pStr[2]: nana
  • pStr[3]: ana
  • pStr[4]: na
  • pStr[5]: a

4. 解法-後綴數組排序

後綴數組中的每個元素都指向一個字符串,如果某個長字符串在數組中串行兩次,那麼它將出現在量不同的後綴中,依次可以對數組進行排序以找到相同的後綴。

因爲數組的每個元素都相當於一個字符串,因此按照字符串的方式進行比較排序,如下所示:

比較函數如下所示:

bool StringCompare(const char *pa, const char *pb)
{
    while (*pa != '\0' && *pb != '\0' && *pa == *pb)
    {
        pa++;
        pb++;
    }

    if (*pa == '\0' && *pb != '\0') /* 小於 */
        return true;
    else if (*pb == '\0') /* 大於等於 */
        return false;
    else
        return *pa < *pb;
}

調用方式如下所示:

/* 對後綴數組進行排序 */
sort(pStr.begin(), pStr.end(), StringCompare);

對“banana”的後綴數組進行排序後如下所示:

  • pStr[0]: a
  • pStr[1]: ana
  • pStr[2]: anana
  • pStr[3]: banana
  • pStr[4]: na
  • pStr[5]: nana

通過比較相鄰元素,可以找到最長的重複子字符串:“ana”

5. 解法-獲取最長重複子串

對後綴數組中的相鄰的元素進行比較,找到最長的重複字符串。

int LengthOfCommon(const char *p1, const char *p2)
{
    int length = 0;
    while (p1[length] != '\0' && p2[length] != '\0' && p1[length] == p2[length])
        length++;

    return length;
}

調用方式如下所示:

/* 依次比較後綴數組的兩個相鄰子字符串 */
int currentMaxLength = 0;
int currentIndex = 1;
for (int i = 1; i < pStr.size(); i++)
{
    int length = LengthOfCommon(pStr[i-1], pStr[i]);
    if (length > currentMaxLength)
    {
        currentMaxLength = length;
        currentIndex = i;
    }
}

/* 提取公共子字符串 */
string result;
for (int i = 0; i < currentMaxLength; i++)
{
    result += pStr[currentIndex][i];
}

6. 流程

  1. 構造後綴數組
  2. 對後綴數組進行排序
  3. 對後綴數組相鄰元素進行比較,查找最長重複子串
  4. 提取最長重複子串

7. 複雜度分析

假設輸入字符串的長度爲n,則:

  • 空間複雜度
    空間複雜度爲O(n),因爲後綴數組保存的並不是n個子字符串,而是指向這n個字符串的指針。

  • 時間複雜度:

    1. 構建後綴數組的時間複雜度爲O(n)
    2. 假設使用快速排序,時間複雜度爲O(knlgn),其中k爲字符串的平均比較次數,一般重複子字符串的長度不會特別大,因此k較小。
    3. 查找最長重複子字符串的時間複雜度爲O(kn)

綜上:空間複雜度爲O(n),時間複雜度爲O(nlgn)

8. 實驗

以英文版的《簡愛》作爲輸入字符串,總共1059723個字符,最長的重複子串爲:

“I am disposed to be gregarious and communicative to-night”。

運行時間爲:0.358s

以英文版的《霧都孤兒》作爲輸入字符串,總共935369個字符,最長的重複子串爲:

“Is a very short one, and may appear of no great importance in its place; but it should be read notwithstanding, as a sequel to the last, and a key to one that will follow when its time arrives.”

運行時間爲:0.325s

以英文版的《霧都孤兒》作爲輸入字符串,總共790103個字符,最長的重複子串爲:

“I am the resurrection and the life, saith the Lord: he that believeth in me, though he were dead, yet shall he live: and whosoever liveth and believeth in me, shall never die.”

運行時間爲:0.278s

9. 其他

參考《編程珠璣》第15章

代碼:GitHub

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章