Problem 3用的又是哈希表,题意为找出一个字符串的最长不包含重复字符子串的长度,看到题目后的第一感觉就是利用动态规划,用类似于寻找相似字串的方法做(事实证明还是太年轻),之后便发现,构出的关系式并不是动态规划的解法,只需要单纯的以各位置的字符作为子串的首字符然后得出最长子串长度,最后找到最大长度。而在以某个字符作为首字符时,将尾字符的位置不断后移,每后移一个字符,放入哈希表里判断是否已存在,存在,则长度已知,否则继续后移。
因此代码如下
int lengthOfLongestSubstring(char* s) {
int repeat[128]={0}, i = 0, j = 0, k, max = 0;
int *l = (int*)malloc(sizeof(int)*strlen(s));
/*
repeat为ASCII码的哈希表,函数关系为f(n)=n(ASCII的值),l数组用来记录一各个位置的字符为起始字符所得到的Longest Substring Without Repeating Characters的长度
*/
for(k=0; k<strlen(s); k++) {
l[k] = 0;
}
/*
初始化l数组
*/
while(s[i] != '\0') {
j = i;
while(s[j] != '\0') {
if(!repeat[s[j]]) {
l[i]++;
repeat[s[j]]++;
j++;
} else {
break;
}
}
i++;
for(k=0; k<127; k++) {
repeat[k] = 0;
}
}
/*
用两个位置记录子串的头尾,i为头,j为尾,i,j的范围是0<=i<=j<strlen(str)
当j指向的尾部字符在哈希表里的值为0时,则将j指向的字符记录于哈希表里,然后j指向下一个字符,以i指向的字符为头的子串的长度加一
当j指向的尾部字符在哈希表里的值为1时,则出现重复字符,跳出循环,以i指向的字符为头的子串的长度确定
i指向下一字符,并清空哈希表
*/
for(k=0; k<strlen(s); k++) {
if(max < l[k])
max = l[k];
}
/*
查找最大值即最大长度
*/
return max;
}
我的程序的时间复杂度是O(n^2),AC后看了一下他给的解法,他提供的三种解法中的第一种的复杂度是O(n^3),他将每种子串的情况(O(n^2))列举出来然后每种情况进行判断(O(n)),因此时间复杂度很高,第二种解法用的类似于滑动窗口的想法,窗口右侧不断扩展,直到扩展的那个字符重复,然后左侧不断收缩直到去掉重复的那个字符,然后右侧再次进行拓展,因此,用C实现的代码如下:
int MAX(int num1, int num2) {
if(num1 > num2) {
return num1;
} else {
return num2;
}
}
int lengthOfLongestSubstring(char* s) {
int hash[128] = {0}, i = 0, j = 0, max = 0;
while(i < strlen(s) && j < strlen(s)) {
if(!hash[s[j]]) {
hash[s[j]] = 1;
j++;
max = MAX(j - i, max);
} else {
hash[s[i]] = 0;
i++;
}
}
return max;
}
第三种解法是基于上面解法的优化,针对于滑窗左侧的优化,而优化的理念在于两个重复字符之间最长的子串位于第一个重复字符的后一个字符到第二个重复字符,这个子串中的所有子串都满足无重复字符,但非最长,因此可以“跳跃式的滑动”,利用哈希表记录字符是否重复出现改为记录字符的位置,如此,左侧窗口可以直接滑到第一个重复字符的后一个字符的位置,减少了左侧窗口的step by step的步骤。因此,设计的代码如下:
int MAX(int num1, int num2) {
if(num1 > num2) {
return num1;
} else {
return num2;
}
}
int lengthOfLongestSubstring(char* s) {
int position[128] = {0}, i = 0, j = 0, max = 0;
while(j < strlen(s)) {
i = MAX(position[s[j]], i);
max = MAX(max, j - i + 1);
position[s[j]] = j + 1;
j++;
}
return max;
}