最初的想法是用一个字典记录读到的字符的位置,若读到重复的字符则记录当前子串长度,和当前最长子串进行比较记录,然后回到这个重复字符上一次出现的地方,重新开始读、记录。需要注意的是,读到重复字符时是回到该字符上一次出现的地方而非该字符第一次出现的地方,我在第一次编码时没有考虑到这一点,于是字典中只存储了0或1来表示该字符是否出现过,若出现过,则用x.index找到字符第一次出现的位置,结果函数一直陷入死循环。
def lengthOfLongestSubstring(s: str) -> int:
loc = 0
chara = {}
maxlen = 0
acclen = 0
while loc < len(s):
check = chara.get(s[loc], 0)
if check == 0:
chara[s[loc]] = loc + 1
acclen = acclen + 1
loc = loc + 1
else:
if acclen > maxlen:
maxlen = acclen
loc = chara[s[loc]]
acclen = 0
chara.clear()
if acclen > maxlen:
maxlen = acclen
return maxlen
然后看到较优解法是滑动窗口,该算法的思想是将出现重复字符时从“窗口”最左侧开始删除,一直删到重复字符为止,并把重复字符删除,该算法遇到重复字符没有将遍历位置回到该字符上一次出现的地方,也就是我的解法,我的解法效率低在需要从重复字符上一次出现的地方后面紧接的一个字符开始再次遍历到重复字符目前最后一次出现的地方,这些工作是不必要的,但是有一个优点是不用逐个删除重复字符之前的字符。从本质上讲滑动窗口是从左往中间删,我的是从中间往右添加,如果不重复字符串较长,明显是滑动窗口效率较高。当然可以在此基础上将滑动窗口进行改进:不逐个删除,也不逐个添加,直接将不重复字符串进行保留,这样同样需要字典存储字符最后一次出现的位置。
下面是我的解法
def lengthOfLongestSubstring(s: str) -> int:
loc = 0
chara = {}
maxlen = 0
acclen = 0
while loc < len(s):
check = chara.get(s[loc], 0)
if check == 0:
chara[s[loc]] = loc + 1
acclen = acclen + 1
loc = loc + 1
else:
if acclen > maxlen:
maxlen = acclen
loc = chara[s[loc]]
acclen = 0
chara.clear()
if acclen > maxlen:
maxlen = acclen
return maxlen
然后是优化后的滑动窗口
def lengthOfLongestSubstring(s: str) -> int:
chara = {}
maxlen = 0
i = 0
for j in range(len(s)):
#这里用max函数而不直接用i = chara.get(s[j], 0)是为防止abcbda这种情况:读到第二个a时,保
#留下来的字符串会将中间的两个b保留下来,也就是说由于没有在字典中删除之前的重复字符,不能保
#证新检测到的重复字符就是保留下来的字符串中的,需要与保留字符串的第一个字符的位置进行比较后
#才能确定
i = max(chara.get(s[j], 0), i)
maxlen = max(maxlen, j - i + 1)
chara[s[j]] = j + 1
return maxlen
考虑到i只有在检测到当前位置为重复字符时才进行更新,可以加上一个判断条件进一步加快速度
def lengthOfLongestSubstring(s: str) -> int:
chara = {}
maxlen = 0
i = 0
for j in range(len(s)):
if s[j] in chara:
i = max(chara.get(s[j], 0), i)
maxlen = max(maxlen, j - i + 1)
chara[s[j]] = j + 1
return maxlen
需要注意一点:不是说在发现重复字符时才更新最长长度,如果试图根据这点进行效率的提升需要加上很多的限制条件,反而得不偿失