[LeetCode]3. Longest Substring Without Repeating Characters寻找最长无重复字符的子串
Given a string, find the length of the longest substring without repeating characters. For example, the longest substring without repeating letters for "abcabcbb" is "abc", which the length is 3. For "bbbbb" the longest substring is "b", with the length of 1.
解法一:枚举出字符串的所有子串,去掉有重复字符的那部分,最后找出其中长度最长的那个。最笨的办法,时间复杂度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; }
解法四:使用后缀数组
对这个字符串构造后缀数组,在每个后缀数组中,寻找没有重复字符的最长前缀,最长的前缀就是要找的子串。
//得到字符串最长的无重复的前缀长度 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[99999]; 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; }
解法五:我们建立一个256位大小的整型数组来代替哈希表,这样做的原因是ASCII表共能表示256个字符,所以可以记录所有字符,然后我们需要定义两个变量res和left,其中res用来记录最长无重复子串的长度,left指向该无重复子串左边的起始位置,然后我们遍历整个字符串,对于每一个遍历到的字符,如果哈希表中该字符串对应的值为0,说明没有遇到过该字符,则此时计算最长无重复子串,i - left +1,其中i是最长无重复子串最右边的位置,left是最左边的位置,还有一种情况也需要计算最长无重复子串,就是当哈希表中的值小于left,这是由于此时出现过重复的字符,left的位置更新了,如果又遇到了新的字符,就要重新计算最长无重复子串。最后每次都要在哈希表中将当前字符对应的值赋值为i+1。
解释下程序中那个if条件语句中为啥要有个m[s[i]] < left,我们用一个例子来说明,当输入字符串为"abbca"的时候,当i=4时,也就是即将要开始遍历最后一个字母a时,此时哈希表表中a对应1,b对应3,c对应4,left为2,即当前最长的子字符串的左边界为第二个b的位置,而第一个a已经不在当前最长的字符串的范围内了,那么对于i=4这个新进来的a,应该要加入结果中,而此时未被更新的哈希表中a为1,不是0,如果不判断它和left的关系的话,就无法更新结果,那么答案就会少一位,所以需要加m[s[i]] < left。
class Solution { public: int lengthOfLongestSubstring(string s) { int m[256] = {0}, res = 0, left = 0; for (int i = 0; i < s.size(); ++i) { if (m[s[i]] == 0 || m[s[i]] < left) { res = max(res, i - left + 1); } else { left = m[s[i]]; } m[s[i]] = i + 1; } return res; } };