原题如下:
Given a string, find the length of the longest substring without repeating characters.
给出一个字符串,计算没有重复字符的最长子串的长度。
Example 1:
Input: "abcabcbb"
Output: 3
Explanation: The answer is "abc", with the length of 3.
Example 2:
Input: "bbbbb"
Output: 1
Explanation: The answer is "b", with the length of 1.
Example 3:
Input: "pwwkew"
Output: 3
Explanation: The answer is "wke", with the length of 3.
Note that the answer must be a substring, "pwke" is a subsequence and not a substring.
思路
从左向右扫描,如果下一字符在之前没有出现过,则继续下去,直到一个重复字符的出现,计算到这里之前的子串的长度,然后继续从该位置向右扫描,继续寻找是否有更长的符合条件的子串,但是下一子串的开头就必须从刚才那个重复字符出现过的位置的下一位置开始。
比如abcad,一开始依次扫描abc,然后到a的时候发现重复了,于是计算当前子串abc长度为3,继续刚才的扫描,下一字符是d,然后结束;因为第一次的时候a是重复字符,所以计算第二个子串长度时应该从b开始,即bcad,长度为4,比刚才的3更长,所以最终结果为4。
想法是这样,但是怎么实现也是个问题。判断字符是否出现过,可以用一个128位(或256位)的数组num[],字符可以对应ASCII中的0~127,数组相应位置的元素用来标识是否出现过,比如可以用num[‘a’]=1表示其已经出现过。但是这样会带来问题,就是如何在识别下一个子串时恢复所有字符的状态,还有如何计算子串的长度。
一种方式是数组对应元素记录该字符在子串中的位置,并在每次遇到一个新子串时记录长度,并更新位置。
代码如下:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int num[256] = {0};
int len = 0;
int maxLen = 0;
for(auto i : s){
if(num[i] != 0){
if(maxLen < len) maxLen = len;
len = len - num[i];
for(int j = 0; j < 256; j++){
if(num[j] != 0 && j != i){
int order = num[j] - num[i];
num[j] = order > 0 ? order : 0;
}
}
}
len++;
num[i] = len;
}
return maxLen >= len ? maxLen : len;
}
};
改进
上述的方法需要在每次遇到新子串都更新一遍数组,这样很影响性能,一个好的改进就是数组记录对应字符最近出现的位置,并用一个变量subStart记录子串开始位置,若字符最近出现的位置在subStart的右边,说明已经重复。
代码如下:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int maxLen = 0;
int index[128] = {0}; // 记录最近下标的下一位置
for(int i = 0, subStart = 0; i < s.size(); i++){
char a = s[i];
subStart = max(index[a], subStart);
maxLen = max(maxLen, i - subStart + 1);
index[a] = i + 1;
}
return maxLen;
}
};