Descripition:
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.
解法1:暴力(Time Limit Exceeded)
这道题的 Solution 里面有暴力破解这种方法,反正我是没用这种方法,想想就蠢,需要检查一个字符串的所有的子串是不是有重复的字符,但是分析滑动窗口的时候需要,我还是把函数贴出来了,
allUnique
a
l
l
U
n
i
q
u
e
函数检查
s[i:j]
s
[
i
:
j
]
是否有重复字符,原版如下:
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int ans = 0;
for (int i = 0; i < n; i++)
for (int j = i + 1; j <= n; j++)
if (allUnique(s, i, j)) ans = Math.max(ans, j - i);
return ans;
}
写博客时,脑子里突然蹦出这种想法:先检查长度为 n 的所有子串,如果其中一个没有重复字符,则直接返回,否则再去检查长度为 n - 1 的所有子串,如果其中一个没有重复字符,则直接返回,这样一直下去直到检查长度为 2 的子串。但是依然超时,所以还是不要想着暴力求解了,改版如下:
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int ans = 0;
for (int j = n; j >= 1; j--)
for (int i = 0; i <= n - j; i++)
if (allUnique(s, i, i + j)) return j;
return ans;
}
解法2:滑动窗口(Accepted)
寻找所有子串慢的原因就在于,当一个子串不满足条件的时候,其实
s
s
中所有包含这个字串的子串也不满足条件,暴力求解无法绕过这些子串。我们可以换一种方式,当时提交的时候没感觉,但是其实我觉得这种方法就是来自于暴力求解,在暴力求解的 函数中,
i
i
固定时, 一直增加即向右滑动,就相当于暴力求解的第二层循环在检查以
s[i]
s
[
i
]
开始的所有子串是不是有重复字符,但是一旦
s[j]
s
[
j
]
在
s[i:j]
s
[
i
:
j
]
(Python的写法,不包括
s[j]
s
[
j
]
)中,就没有必要继续检验下去了,因为既然
s[i:j+1]
s
[
i
:
j
+
1
]
有重复字符,
s[i:k](k>j+1)
s
[
i
:
k
]
(
k
>
j
+
1
)
必然有重复字符,这种情况下直接遍历下一个
i
i
就行了,即将 加1就行了,即
i
i
向右滑动一个单位。将上诉想法转化为代码即:
class Solution(object):
# Approach #2 Sliding Window [Accepted]
def lengthOfLongestSubstring(self, s):
submax = pcur = pstar = 0
while pcur < len(s):
if s[pcur] in s[pstar:pcur]:
if pcur - pstar > submax:
submax = pcur - pstar
pstar += 1
else:
pcur += 1
if pcur - pstar > submax:
return pcur - pstar
return submax
解法3:改进的滑动窗口(Accepted)
上面程序其实还有可以改进的地方,首先检查第六行检查 s[pcur] in s[pstar:pcur]
需要的时间复杂度为 ,其实我们可以降低到
O(1)
O
(
1
)
,既然在
i
i
没有进行下一次滑动之前的字符串没有重复字符,我们可以用字典(Java 里面的 Map)完成检查,假设我们可以完成替代,也至少需要 时间复杂度(最坏情况下,每个字符被访问两次)。如果
s[pcur]
s
[
p
c
u
r
]
在
s[pstar:pcur]
s
[
p
s
t
a
r
:
p
c
u
r
]
中,假设为
pcur′
p
c
u
r
′
,我们并不需要一点一点增加
pstar
p
s
t
a
r
,我们可以跳过
s[pstar:pcur′]
s
[
p
s
t
a
r
:
p
c
u
r
′
]
范围内的所有字符串,并且让
pstar
p
s
t
a
r
直接成为
pcur′
p
c
u
r
′
。上述思想转化成 Python 程序如下,可以看出这里最坏情况下,每个字符只被访问一次:
class Solution(object):
def lengthOfLongestSubstring(self, s):
# Approach #3 Sliding Window Optimized [Accepted]
indexes = {}
longest = 0
last_repeating = -1
for i, c in enumerate(s):
if c in indexes and last_repeating < indexes[c]:
last_repeating = indexes[c]
if i - last_repeating > longest:
longest = i - last_repeating
indexes[c] = i
return longest
分享楼主提交过程中两个错误的提交:
错误提交1:pstar 跳的太快了
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
submax = pcur = pstar = 0
scur = ""
while pcur < len(s):
if s[pcur] in scur:
if pcur - pstar > submax:
submax = pcur - pstar
pstar = pcur
scur = ""
else:
scur += s[pcur]
pcur += 1
if len(scur) > submax:
return len(scur)
return submax
一个错误的测试用例:
Input: “dvdf”
Output: 2
Expected: 3
错误提交2:pstar 跳的太快了,就算从结尾再向前扫描一边还是修正不了错误。
class Solution(object):
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
submax = pcur = pstar = 0
scur = ""
while pcur < len(s):
if s[pcur] in scur:
if pcur - pstar > submax:
submax = pcur - pstar
pstar = pcur
scur = ""
else:
scur += s[pcur]
pcur += 1
if len(scur) > submax:
submax = len(scur)
pcur = pstar = len(s) - 1
scur = ""
while pcur > -1:
if s[pcur] in scur:
if pstar - pcur > submax:
submax = pstar - pcur
pstar = pcur
scur = ""
else:
scur += s[pcur]
pcur -= 1
if len(scur) > submax:
return len(scur)
return submax
一个错误的测试用例:
Input: “asjrgapa”
Output: 5
Expected: 6