参考链接:力扣
题目:
给定一个字符串s,请你找出其中不含有重复字符的最长子串的长度
示例:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
思路:
1.经典解法
遍历。从i=0出发的最长无重复字符串长度,然后i=1,i=2依次判断...
每次i位置,都需要求解最长不重复子串,有两种情况:
a. 字符串本身
b. 遍历过程中遇到了出现过的字符
代码:
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
s_list = [i for i in s]
result_rlen = 0
for i in range(len(s_list)):
# 从i开始的子字符串列表
i_list = s_list[i:]
i_result = [] # i位置开始的不重复子字符列表
for char in i_list:
if char in i_result:
break
i_result.append(char)
# 上述遍历完成,为i位置开始的不重复列表
result_rlen = max(result_rlen, len(i_result))
return result_rlen
可以看到这个方法非常慢,整个过程就是遍历拿i位置出发的不重复字符串列表,拿的过程,就是遍历过程,一旦遇到重复的就跳出
时间复杂度O(N^2)
2.进阶解法
滑动窗口法:滑动窗口法是一种算法思想,一种相对大众的解题思路,具体是谁提出的不详,类似于二分法,是属于算法训练中常用的思想。
过程:
建立一个窗口lookup,左指针left
遍历数组,不停的将字符加入窗口中,若出现重复字符,从左边开始去掉重复字符,缩小窗口,过程中记录无重复窗口的长度,遍历完成后返回最大长度
核心:在于维护了一个无重复窗口,并不是遍历完成后,窗口里就是最长无重复子串,比如一个反例
abcdaede
过程如下:
a
ab
abc
abcd
abcda->bcda
bcdae
bcdaed->aed
aede->de
过程中的最长无重复子串为bcdae,最长长度为5
代码思路:
"""
思路已经清晰:
窗口lookup lookup=set()
左指针left left = 0
最大长度 max_len = 0
窗口加入字符 lookup.add(s[i])
维护一个窗口,如果有重复,从左边删除字符,直到没有当前字符 while循环
while s[i] in lookup:
lookup.remove(s[left])
left += 1
过程中记录窗口的长度,一直维持一个最大长度,最后遍历完成返回
max_len = max(max_len, len(lookup))
"""
【算法到代码的翻译】
这一步其实没有看上去这么简单,需要考虑清楚先干啥后干啥
遍历过程中:
1.判断当前元素,删窗口重复元素
2.加当前元素
3.维护最大长度
这个过程一定不能乱,核心不在于加元素,也不在于维护最大长度,在于维护干净的无重复窗口lookup,维护由两步组成,先删后加或者先加后删,取长度一定是最后一步。
为什么不能先加后删呢?
因为先加后删,根本第一步都开始不了,所以只能先删后加。
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if not s:
return 0
lookup = set()
left = 0
max_len = 0
for i in range(len(s)):
# 上来就判断是否在窗口中,相当于如果有重复的,会先删干净窗口
while s[i] in lookup:
lookup.remove(s[left])
left += 1
# 再加元素到窗口,此时窗口一定不重复
lookup.add(s[i])
# 再维护最大窗口长度max(max_len, len(lookup))
max_len = max(max_len, len(lookup))
return max_len
效果:
可以发现,优化了非常多,同时说明,该种方法并不是最佳解法,最佳的解法往往都是一步一步磨出来的,所以不要奢求一步到位,该代码虽然不是最佳,但已经足以应付面试。
考察点:数组,滑动窗口法,滑动窗口法的实现