给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
字符串中任意个连续的字符组成的子序列称为该串的子串。
提示:
题解来源:力扣官方题解
寻找字符串中不含重复字符的最长字串,一个很直观的方法是找到无重复字符的所有子串,其中最长的子串即为所求。
这里官方题解中用了一个双指针的方法,左指针表示字串的起始位置 k k k,右指针表示从当前左指针开始得到的最长无重复字符的子串的结束位置 r k r_k rk?。这里值得注意的是,当我们从 k k k 位置开始,找到了一个无重复字符的最长子串,其结束位置在 r k r_k rk?,则当我们滑动左指针到 k + 1 k+1 k+1 时,左指针到 r k r_k rk? 之间仍然是无重复字符的子串。利用这个规律可以减少双指针窗口滑动过程的判断次数。
在Python当中,判断是否包含重复值可以用列表list、集合set等存储无重复字符,这里常用哈希集合set。在“窗口滑动”这个思路当中,右指针是一直递增的,而左指针也一直往右滑动,两个指针都只滑动一遍整个字符串,相当于每层循环乘以的系数之和为 n n n,时间复杂度为 O ( n ) O(n) O(n),而空间复杂度为存储的无重复字符的子串,在最差情况下,存储了问题的字符集的全部元素。
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
# 哈希集合,记录每个字符是否出现过
occ = set()
n = len(s)
# 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
rk, ans = -1, 0
for i in range(n): # 左指针
if i != 0:
# 左指针向右移动一格,移除一个字符
occ.remove(s[i - 1])
while rk + 1 < n and s[rk + 1] not in occ:
# 不断地移动右指针
occ.add(s[rk + 1])
rk += 1
# 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = max(ans, rk - i + 1)
return ans
执行用时:244 ms
消耗内存:17.02 MB
上面的双指针的操作很直观,也很简单实现,但是大家有没有发现,当双指针窗口滑动的时候,碰到一个新的重复字符时,并不知道新的重复字符在原来子串当中的位置,所以只能将左指针一格格地往右移动。
例如:字符串“abcdc”,当取到 “abcd” 子串时,右指针滑动到最后一位 “c” 时,判断得到该字符在已有子串中,但是不知道具体位置,所以左指针需要从“a” 移动到 “b” 再到 “c”,而如果能直接存储每个字符的上一次出现的位置,则在新增重复字符时,直接将左指针移动到上一次出现的位置,减少了左指针的遍历次数。
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
max_len, hashmap = 0, {}
# 定义窗口的首尾端 (start, end), 然后滑动窗口
start = 0
for end in range(len(s)):
# 存储字符的出现频率,并更新子串最大长度
hashmap[s[end]] = hashmap.get(s[end], 0) + 1
if len(hashmap) == end - start + 1:
max_len = max(max_len, end - start + 1)
# 当满足下面条件时,说明新增字符已经在哈希表当中,此时更新 start
while end - start + 1 > len(hashmap):
head = s[start]
hashmap[head] -= 1
if hashmap[head] == 0:
del hashmap[head]
start += 1
# 返回答案 (最大长度)
return max_len
执行用时:68 ms
消耗内存:17.02 MB
参考来源:Eason