题目
Given a string, find the length of the longest substring without repeating characters.
Examples:
Given "abcabcbb"
, the answer is
"abc"
, which the length is 3.
Given "bbbbb"
, the answer is "b"
, with the length of 1.
Given "pwwkew"
, 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.
分析
这道题目就是给你一个字符串,然后让你求出该字符串中最长的子串,该子串不包含重复的字符。
要想求出符合要求的最长子串,一般来说历遍整个字符串是必须的。那么,我们如何判断某个子串中的字符是否重复呢?
我们可以记录已经包含在子串中的字符,然后每添加一个新的字符,就与我们的记录相比较,这样就只要有没有重复了。在这里我新建了一个长度为256的数组用于记录已经包含在子串中的字符,下标对应字符的ASCII值,值对应字符在字符串(原始字符串)中的位置。初始化为-1,这样我们就可以根据数组的值来判断是否重复。
解决了判断重复的问题之后,如果出现了重复的,我们又该怎么办呢?
这时我们就可以用一个变量来记录子串第一个字符在给出的字符串中的位置。当发现重复之后,就更新子串第一个字符的位置,使得子串中没有重复的字符。更新后的子串第一个字符的位置应该是之前重复字符的位置加一。同时更新记录表中该重复字符的位置。
例如:对于字符串 abcdbaef。在遍历到第五个字符b的时候,之前我们记录的字符以及其位置如下(位置从0开始)
字符 | a | b | c | d |
位置 | 0 | 1 | 2 | 3 |
注意:我们在更新子串第一个字符的位置之后,没有把记录表中小于该位置的字符去掉,因此记录表中有些字符不属于我们更新之后的子串。所以在判断重复的时候,如果某字符在记录表中记录的位置小于子串第一个字符的位置,直接更新该字符的位置就可以,不需要更新子串第一个字符的位置。例如上面遍历到第二个a的时候,由于a记录的位置为 0 < 2 ,直接更新 a 的位置为5即可。
在遍历的过程中,我们要记录当前子串的长度和已经找到的子串的最大长度。
代码
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// cur_len 为当前子串的长度, temp表示字符的ASCII值, valid表示当前子串第一个字符的位置,
// max_len 为已经找到的子串的最大长度
int cur_len = 0, temp, valid = 0, max_len = 0;
int characters[256]; // 记录每个字符所在的位置,就是上面所说的记录表
for (int i = 0; i < 256; i++) {
characters[i] = -1;
}
for (int i = 0; i < s.length(); i++) {
temp = s[i];
// 记录字符的位置,若重复,则更新字符的位置。
if (characters[temp] == -1) {
characters[temp] = i;
cur_len++;
} else if (characters[temp] >= valid) {
valid = characters[temp] + 1; // 更新子串中第一个字符的位置
characters[temp] = i;
max_len = max_len > cur_len ? max_len : cur_len;
cur_len = i - valid + 1;
} else {
characters[temp] = i;
cur_len++;
}
}
max_len = max_len > cur_len ? max_len : cur_len;
return max_len;
}
};
其实,在更新子串中第一个字符位置的时候,可以将已经找到的最大子串的长度与剩下子串的长度作比较,以此来确定是否继续遍历字符串。