求字符串中最長無重複字符的子串

題目:求一個字符串中最長的沒有重複字符的子串。

方法一:窮舉法,使用2重外循環遍歷所有的區間,用2重內循環檢驗子串是否符合“無重複字符”這一要求。其中外層循環i、j 遍歷所有的下標,m、n是內層循環,檢查區間[i,j]是否符合要求。空間複雜度是O(1),時間複雜度O(N^4)。

複製代碼
//O(N^4)的時間複雜度
int max_unique_substring1(char * str)
{
    int maxlen = 0;
    int begin = 0;
    int n = strlen(str);
    for(int i=0; i<n; ++i)
        for(int j=1; j<n; ++j)
        {
            int flag = 0;
            for(int m=i; m<=j; ++m)
            {
                for(int n=m+1; n<j; ++n)
                {
                    if(str[n] == str[m])
                    {
                        flag = 1;
                        break;
                    }
                }
                if(flag == 1) break;
            }
            if(flag==0 && j-i+1>maxlen)
            {
                maxlen = j-i+1;
                begin = i;
            }
        }
    printf("%.*s\n", maxlen, &str[begin]);
    return maxlen;
}
複製代碼

方法二:對方法一的檢驗子串是否“無重複字符”進行改進,使用hash表記錄字符是否出現過。

複製代碼
//O(N^2)的時間複雜度
int max_unique_substring2(char * str) 
{
    int i,j;
    int begin;
    int maxlen = 0;
    int hash[256];
    int n = strlen(str);
    for(i=0; i<n; ++i)
    {
        memset(hash,0,sizeof(hash)); 
        hash[str[i]] = 1;
        for(j=i+1; j<n; ++j)
        {
            if(hash[str[j]] == 0)
                hash[str[j]] = 1;
            else
                break;
        }
        if(j-i > maxlen)
        {
            maxlen = j-i;
            begin = i;
        }
    }
    printf("%.*s\n", maxlen, &str[begin]);
    return maxlen;
}
複製代碼

方法三:對字符串“axbdebpqawuva”構造下表:

表中,字符串有3個‘a’,有2個‘b’,其餘爲單一字符。next[]記錄了下一個與之重複的字符的位置,如str[0]=str[8]=str[12]=‘a’,這時next[0]=8,next[8]=12,next[12]=13,其餘同理。值得注意的是,對於沒有重複字符的,next[]存儲字符結束符‘\0’的下標,即13。
這裏,first[i]表示i之後,第一次出現重複字符的那個位置。例如,str[0]之後,第一次出現的重複字符是str[5]=‘b’,當然,從str[1],str[2]開始也是一樣。而從str[3]開始,要到str[12]纔出現重複字符‘a’。可以證明,從str[i]起的最長符合要求的長度爲first[i]-i,區間爲[i,first[i]-1]由此得解。上述最長串是當i=3時,first[i]-i=12-3=9。結果最長無重複子串爲“debpqawuv”。

複製代碼
//O(N)的時間複雜度
int max_unique_substring3(char * str) 
{
    int maxlen = 0;
    int begin = 0;
    int n = strlen(str);
    int * next = (int*)malloc(sizeof(int)*n); //next[i]記錄了下一個與str[i]重複的字符的位置
    int * first = (int*)malloc(sizeof(int)*(n+1)); //first[i]記錄str[i]後面最近的一個重複點
    int hash[256];
    memset(hash,n,sizeof(hash));

    first[n] = n;
    for(int i=n-1; i>=0; i--)
    {
        next[i] = hash[str[i]];
        hash[str[i]] = i;
        if (next[i] < first[i+1])
            first[i] = next[i];
        else
            first[i] = first[i+1]; //生成first[]表,複雜度是O(N)的
    }
    for(int i=0; i<n; i++)
    {
        if (first[i]-i > maxlen)
        {
            maxlen = first[i]-i;
            begin = i;
        }
    }
    free(first);
    free(next);
    printf("%.*s\n", maxlen, &str[begin]);
    return maxlen;
}
複製代碼

另一種實現:visit[]記錄每次字符出現的位置,當出現重複字符時,通過兩次重複字符的位置得到新的子串的長度,但是,每次只通過重複字符的位置得到新的子串的長度是不對的,還需要考慮上一次子串的開始位置。

複製代碼
//O(N)的時間複雜度
int max_unique_substring3(char * str)
{
    int visit[256];
    memset(visit, -1, sizeof(visit));
    int n = strlen(str);
    int maxlen = 0;
    visit[str[0]] = 0;
    int curlen = 1;
    int last_start = 0;
    int begin;
    for(int i=1; i<n; ++i)
    {
        if(visit[str[i]] == -1)
        {
            ++curlen;
            visit[str[i]] = i; // 記錄字符出現的位置
         }
        else
        {
            if(last_start <= visit[str[i]])
            {
                curlen = i - visit[str[i]];
                last_start = visit[str[i]] + 1; //跟新下一次開始的位置
                   visit[str[i]] = i; // 更新最近重複位置
              }
            else
            {
                ++curlen;
            }
        }
        if(curlen > maxlen)
        {
            maxlen = curlen;
            begin = i + 1 - maxlen;
        }
    }
    printf("%.*s\n", maxlen, &str[begin]);
    return maxlen;
}
複製代碼

方法四:使用後綴數組

對這個字符串構造後綴數組,在每個後綴數組中,尋找沒有重複字符的最長前綴,最長的前綴就是要找的子串。

複製代碼
//得到字符串最長的無重複的前綴長度
int longestlen(char * p)
{
    int hash[256];
    int len = 0;
    memset(hash,0,sizeof(hash));
    while (*p && !hash[*p])
    {
        hash[*p] = 1;
        ++ len;
        ++ p;
    }
    return len;
}

//使用後綴數組解法
int max_unique_substring4(char * str)
{
    int maxlen = -1;
    int begin = 0;
    char *a[999];
    int n = 0;
    while(*str != '\0')
    {
        a[n++] = str++;
    }
    for (int i=0; i<n; i++)
    {
        int temlen = longestlen(a[i]);
        if (temlen > maxlen)
        {
            maxlen = temlen;
            begin = i;
        }
    }
    printf("%.*s\n", maxlen, a[begin]);
    return maxlen;
}
複製代碼

 

 

作者:阿凡盧
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章