題目:求一個字符串中最長的沒有重復字符的子串。
方法一:窮舉法,使用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; }