1. 题目来源
2. 题目解析
方法一:双指针
很经典的一个双指针算法。如何将一个暴力算法优化成双指针算法呢?主要是需要找到单调性。简单聊一下从暴力到双指针的这个过程:
- 暴力算法: 两层循环,枚举第
i
个位置的字母,再枚举[0, i]
位置,这样就能枚举得到以i
位置结尾的所有子串。将合法的子串记录下来,长度取个max
即可。时间复杂度是 O ( n 2 ) O(n^2) O(n2) 的。 - 双指针算法: 假设子串终点位置为
i
,那么一定有一个最靠左的子串起点j
与当前这个i
位置一一对应。当i
向后移动时,j
就不可能再向前移动了,因为若j - 1
和i + 1
构成了合法情况。那么,j - 1
也一定能和i
构成合法情况。这个违反了j
的定义。故j
实际上是不可能再向前移动的,它最多只能保持不动或者发生重复情况向后移动。故j
具有单调性。那么当我们顺序枚举i
时,起初i
和j
都在首字符,即起点 0 的位置,当i ++
时,若不发生重复,则j
不需要向后移动。若发生重复,则只可能是s[i]
这个字符和s[j~i - 1]
这个区间中的某个字符发生重复,因为j
不能向前移动!所以,就一直向后移动j
,直至找到当前i
位置的最靠左的j
位置,即对于i
的最佳且合法的子串起点位置。
这个是很经典很经典的双指针算法。十分有利于理解双指针算法的本质,且思路并不是那么好想。很值得学习~
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
代码:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> hash;
int res = 0;
for (int i = 0, j = 0; i < s.size(); ++i) {
// 将当前字符添加进哈希表中
hash[s[i]]++;
// 双指针如果哈希表中字符重复出现了,只可能是s[i]重复
// 就将j指针一直向前删除,直到删了这个重复字符
// 并将该重复字符在哈希表的数量减1
// 精髓在于 hash[s[j++]]--; 哈希表维护[j,i]区间内的所有字符的出现次数,实现O(1)的查找
while (um[s[i]] > 1) hash[s[j++]]--;
res = max(res, i - j + 1);
}
return res;
}
};
2024年08月23日03:07:17
更新:这个时候,这题的思路已经是如此自然。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.size();
int res = 0;
unordered_map<char, int> um;
for (int l = 0, r = 0; r < n; r ++ ) {
um[s[r]] ++ ;
// 这里写不写这个 l<=r,取不取等号也无所谓的
// 因为当 l==r 时,um[s[r]] 必然为 1,就是它自身
// 必然不会再次进入 while,造成 l++ 的越界行为
while (l <= r && um[s[r]] > 1) um[s[l ++ ]] -- ;
res = max(res, r - l + 1);
}
return res;
}
};