一. Longest Substring Without Repeating Characters
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.
Difficulty:Medium
解法
首先想到的当然是用哈希表保存出现的字符,并记录该字符在字符串中的位置。循环的时候,如果发现重复的字符,就记录下哈希表中所有字符的数目,并清空哈希表,然后将循环的索引定位为哈希表中保存的重复字符位置加一(因为重复字符之前的字符串可以直接舍弃),继续循环,代码如下:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> info;
int len = 0;
for (int i = 0; i < s.size(); i++) {
if (info.find(s[i]) == info.end()) {
info[s[i]] = i;
}
else {
i = info[s[i]];
if (info.size() > len)
len = info.size();
info.clear();
}
}
if (info.size() > len)
len = info.size();
return len;
}
代码的时间复杂度介于 O(n) 与 O(n2) 之间。
优化
上面的代码还有很多可以优化的地方:
- 首先我们知道map使用红黑树实现的,查询复杂度为 O(log2n) ,而unordered_map是用哈希表实现的,查询复杂度接近于 O(1) 。再看这道题,哈希键的范围是确定的,而且字符是可以隐式转换为数字的,因此可以直接使用vector或者是数组来实现哈希,那么查询复杂度就完全等于 O(1) 了
- 另外一点就是程序中重置了数组的索引,这并不是必要的,我们可以采用双索引的机制,另一个索引,假如命名为begin,指向不重复串起始的位置,而串的长度就是循环的索引减去begin的值。
- 有了双索引之后,我们就可以不必清空哈希表了(之所以清空哈希表是因为担心无法判断之前截断字符串中重复的字符),因为如果新字符的位置比begin小的话,说明这个字符要么没有出现过,要么是之前截断字符串中的字符,可以加入当前不重复子串中。
int lengthOfLongestSubstring(string s) {
vector<int> info(256, -1); // 字符数目的ASCII码值不会大于256
int len = 0, begin = -1; //begin要从-1开始,就是指向不重复子串开始位置的前一个位置
for (int i = 0; i < s.size(); i++) {
if (info[s[i]] > begin)
begin = info[s[i]];
info[s[i]] = i;
if (i - begin > len)
len = i - begin;
}
return len;
}
代码的时间复杂度为 O(n) 。
总结
字符在字符串中的位置往往能提供很多额外的信息,比如这里的可以通过位置区分该字符是和截断的字符串中的字符重复还是和不重复子串中的字符重复,从而降低了代码的复杂度。但是如果不是因为双索引,也不是特别容易往这个方向想。