Leetcode: Longest Substring Without Repeating Characters
Given a string s
, find the length of the longest substring without repeating characters.
Example 1:
Input: s = "abcabcbb"
Output: 3
Explanation: The answer is "abc", with the length of 3.
Example 2:
Input: s = "bbbbb"
Output: 1
Explanation: The answer is "b", with the length of 1.
Example 3:
Input: s = "pwwkew"
Output: 3
Explanation: The answer is "wke", with the length of 3.
Notice that the answer must be a substring, "pwke" is a subsequence and not a substring.
Example 4:
Input: s = ""
Output: 0
Constraints:
0 <= s.length <= 5 * 104
s
consists of English letters, digits, symbols and spaces.
题意:给出一个字符串,要求找出最长的字母不重复的子串,下面介绍几种解法。
solution 1: 直接暴力搜索法
C++ code:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int l = s.size();
int ans = 0;
string s1;
for(int i = 0; i < l; i++){
s1 = s[i];
for(int j = i+1; j < l; j++){
if (s1.find(s[j]) == s1.npos){
s1 += s[j];
}
else break;
}
int k = s1.size();
ans = max(ans, k);
}
return ans;
}
};
提交结果:
Time Submitted | Status | Runtime | Memory | Language |
---|---|---|---|---|
11/02/2020 14:15 | Accepted | 60 ms | 7.2 MB | cpp |
这种最直接的暴力解法居然还能60ms通过,真的是不可思议!
solution 2: 集合暴力搜索
这种方法也很直接,只不过换成了用集合来存储不重复的子串
C++ code:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int res = 0;
unordered_set<char> st;
for(int i = 0; i < s.size(); i++){
st.insert(s[i]);
for(int j = i + 1; j < s.size(); j++){
if (!st.count(s[j])){
st.insert(s[j]);
}
else break;
}
res = res > st.size() ? res : st.size();
st.clear();
}
return res;
}
};
Python code:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
res = 0
for i in range(len(s)):
st = {s[i]}
for j in range(i+1, len(s)):
if s[j] not in st:
st.add(s[j])
else:
break
res = max(res, len(st))
return res
Time Submitted | Status | Runtime | Memory | Language |
---|---|---|---|---|
11/03/2020 22:48 | Accepted | 1272 ms | 150.4 MB | cpp |
11/02/2020 16:13 | Accepted | 444 ms | 14.3 MB | python3 |
一项快速著称的C++用set居然这么慢,差点都TLE,而隔壁Python也是一摸一样的集合方法,却比C++还快,当然,我们能看到处理字符串的时候,集合似乎比用原生string要慢,毕竟solution1只用了60ms。
下面再来看看重头戏,滑动窗口(Sliding Window)法解题,复杂度一下子降到了O(n)
solution 3: 滑动窗口(Sliding Window)法
在传统方法中,我们不断重复地检查一个子串是否含有重复的字母,但是这是不必要的。
算法的本意是,在一些对区间有所限制的问题当中,我们可以通过维护合法区间的左右边界。通过区间移动找出所有合法的区间,最后找到最终的答案。在本题中,我们可以把一个不包含重复字符的子串当作是原字符串的一个合法区间。例如"abcabcbb"
中[0,2]就是一个合法区间。我们用两个记录下标的指针l和r来记录这个区间的左右端点,这里两边都是用的闭区间,也就是l = 0, r = 2
。
那么,如何移动区间呢?
[a b c] a b c b b
很简单,每次往右移动直至遍历整个字符串即可,也就是r += 1
,于是变成:
[a b c a] b c b b
r
每次往右移动一位,就会读入一个新的字符,那样整个区间的合法性就可能被破坏掉,例如上面,又读入了一个a
,这样就不是合法空间了。
但是没关系,我们还有左边界的l
指针,我们通过移动l
指针,退出一些字符,直到原区间变得合法为止,在这个例子中,l向右移动一位即可:
a [b c a] b c b b
也就是说,新区间变成了[1,3]。如果r
移动后,没有出现重复字符则继续移动,区间字符个数就是我们要找的不重复字符子串的长度,即r - l + 1
。我们只需要在每次的合法区间里维护一个最大值就可以了,这个最大值就是我们返回的结果。
维护合法空间的具体方法是维护一个map
,用来存放字符和它出现的索引之间的映射。下面就来看代码了:
Python code:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
l, res = 0, 0
map = {}
for r in range(len(s)):
if s[r] in map:
# 防止出现abba的情况
l = max(l, map[s[r]] + 1)
map[s[r]] = r
res = max(res, r - l + 1)
return res
C++ code1:(using map)
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int l = 0, res = 0;
map<char, int> mp;
for(int r = 0; r < s.size(); r++){
if (mp.count(s[r])){
l = max(l, mp[s[r]] + 1);
}
mp[s[r]] = r;
res = max(res, r - l + 1);
}
return res;
}
};
C++ code2:(using unordered_map)
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int l = 0, res = 0;
unordered_map<char, int> mp;
for(int r = 0; r < s.size(); r++){
if (mp.count(s[r])){
l = max(l, mp[s[r]] + 1);
}
mp[s[r]] = r;
res = max(res, r - l + 1);
}
return res;
}
};
C++ code3:(using map.find()查找)
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int l = 0, res = 0;
unordered_map<char, int> mp;
for(int r = 0; r < s.size(); r++){
if (mp.find(s[r]) != mp.end()){
l = max(l, mp[s[r]] + 1);
}
mp[s[r]] = r;
res = max(res, r - l + 1);
}
return res;
}
};
Python使用滑动窗口算法把时间降到了60ms,C++最终能降到20ms
C++的STL里面对set和map实现了两种形式,即set, map
和unordered_set, unordered_map
,其中第一种采用的是红黑树实现的,插入和删除操作速度快,而第二种的优点则是查找速度快,在本题中会频繁使用查找操作,因此unordered_map肯定是比map快的。code1和code2的区别就是使用的map不一样。而map中查找键的函数一般有两个,一个是find()
,一个是count()
,经测试,find会比count快。code2和code3就是查找函数用的不一样,下面来看看code1~code3所花时间对比:
Time Submitted | Status | Runtime | Memory | Language |
---|---|---|---|---|
11/03/2020 15:07 | Accepted | 60 ms | 14.3 MB | python3 |
11/03/2020 16:58 | Accepted | 44 ms | 8.8 MB | cpp(code1) |
11/03/2020 17:02 | Accepted | 28 ms | 8.8 MB | cpp(code2) |
11/03/2020 17:09 | Accepted | 20 ms | 8.8 MB | cpp(code 3) |
滑动窗口算法博大精深,还需多看代码,多领悟!
谢谢观看!