题目地址:力扣
解法1:两遍遍历
思路:因为字符仅限于英文字母、数字、空格和符号,那么可以用ASCII码来表示,因此开辟一个128大小的bool类型的map用于存放每个字符的情况,初始设置为false,若遍历过了则设为true。从字符串头开始,找从当前头往后的最长无重复子串,并且更新全局最大的子串长度,一直找到末尾(可以设置终止条件提前终止)。
class Solution {
public:
// 用于初始化map,每次遍历完都重置状态
void initmap(map<int, bool> &cmap)
{
for (int i = 0; i < 128; ++i)
cmap[i] = false;
}
int lengthOfLongestSubstring(string s) {
// sz用于保存不重复子串的全局最大长度,cmap用于存储每个字符状态
int sz = 0;
map<int, bool> cmap;
// 遍历以字符串的每个字符开头的最长不重复子串
for (int i = 0; i < s.size(); ++i)
{
initmap(cmap);
// 用于存储以当前字符开头的最长子串长度
int cur_sz = 0;
// 当前字符开头向后找
for (int j = i; j < s.size(); ++j)
{
// 若字符未出现过,就把状态置为出现,并且递增当前长度,更新全局长度
if (!cmap[s[j]])
{
cmap[s[j]] = true;
++cur_sz;
sz = cur_sz > sz ? cur_sz : sz;
// 字符出现过,那么就已经找到了以当前字符开头的最长不重复子串
} else {
break;
}
}
// 若最长子串的长度加上i大于字符串总长度,后面的就不要找了,长度一定更小
if (sz + i >= s.size())
break;
}
return sz;
}
};
解法2:滑动窗口法
思路:我们注意到,在上一种方法里,找到以某个字符为开头的最长不重复子串的循环终止条件就是当前子串的中某处和子串尾出现了相同的字符。因此这就告诉我们了,如果把当前子串的头移动到子串中某处的后一位,那么当前子串一定是一个不重复的串,所以这个信息也就意味着我们可以重复利用这一个串一直往后找,因此就没必要每轮都从起始的字符的头开始找。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// sz为全局不重复子串最长长度,cset用于存储出现过的字符
int sz = 0;
unordered_set<char> cset;
// end用于存储尾部指针
int end = 0;
// 头没到字符串尾循环继续
for (int i = 0; i < s.size(); ++i)
{
// end没到字符串尾循环继续
for (; end < s.size(); ++end)
{
// 如果当前的字符未出现过,就插入当前字符并且更新全局最长长度
// 这里每找一次更新一次主要是为了应对字符串长度为1的情况
if (cset.count(s[end]) == 0)
{
cset.insert(s[end]);
sz = (end - i + 1) > sz ? (end - i + 1) : sz;
}
// 若当前字符出现过
else
{
// 头一直移动到当前字符的位置,并且把经历过的每个字符都从set中清除
// 由于发现重复字符时并未重复添加,因此这里也不需要清除,只需要把end往后
// 移动,然后跳出循环即可
while (s[i] != s[end])
cset.erase(s[i++]);
++end;
break;
}
}
}
return sz;
}
};
解法3:滑动窗口法(时间再优化)
上面的滑动窗口法在发现字符重复之后,需要一个个移动头来找到下一个头应该在的位置,这个步骤可以使用map存对应的下标来进行时间的优化。而且上面用的是头指针来控制循环的,但我们知道,实际上尾指针走到了字符串末尾,最长不重复的子字符串长度就可以确定下来,结束循环,因此这里我们采用尾指针来控制循环。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// sz保存全局最长不重复子串长度,cmap保存字符和出现的下标位置
int sz = 0;
unordered_map<char, int> cmap;
// 头指针和尾指针都置为0
int end = 0, start = 0;
// 只要尾指针不到尾就继续循环
while (end < s.size())
{
// 如果当前字符出现过
if (cmap.find(s[end]) != cmap.end())
{
// 若当前字符出现的位置在头指针之后,那么说明子串出现了重复字符
if (cmap[s[end]] >= start)
{
// 更新长度,并且更新头指针位置
sz = max(sz, end - start);
start = cmap[s[end]] + 1;
}
}
// 无论字符是否出现过,都更新当前元素的位置,并且尾指针向后移动
cmap[s[end]] = end;
++end;
}
// 当尾指针走到末尾的时候再更新一次长度
sz = max(sz, end - start);
return sz;
}
};
Accepted
- 987/987 cases passed (16 ms)
- Your runtime beats 70.01 % of cpp submissions
- Your memory usage beats 50.85 % of cpp submissions (8.8 MB)