LeetCode 3-无重复字符的最长子串

124 阅读4分钟

题目

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列, 不是子串。

 

提示:

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成

思路

解法一:双指针+列表存储字符(自)

理清思路:

  • 如果区间[a...b]包含了相同元素,那么所有包含[a...b]的区间都有重复值,只能从[a+1...b]开始找。
  • 如果区间[a...b]不包含相同元素,那么这个区间内的任意子区间都不包含相同元素。

在对前两个小点认识清楚后,就知道如果我们维护一个不重复的列表,那么每次循环的值,需要判断是否在这个不重复列表中,如果在,需要找到它在列表的位置j,然后从j+1开始为最新的无重复字符串的子串的起点。

步骤

用一个records保存遍历遇到的所有元素,然后每次遍历一个就查询是否在records中,如果在,找到s[i]在records中的位置index,将records从index+1开始截断,只保留后面的元素。然后统计records的最长长度就是答案。

效率很低,只打败了19%,猜测是因为records保存了所有不重复的元素,另外还在records的index查找的原因。

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        i = 0 # 遍历的指针
        n = len(s)
        res = 0
        records = []
        while i < n:
            if len(records) == 0:
                records.append(s[i])
                res = max(res, 1)
            else:
                while len(records) > 0 and s[i] in records:
                    records = records[records.index(s[i])+1 :]
                records.append(s[i])
                res = max(res, len(records))
            i += 1
        return res

解法二:双指针+hash存储字符位置

解法一的麻烦之处在于s[i]在records中index的查询,可以把这个抽出来,用一个hash保存{字符:索引},这样对于出现的重复元素很快就能定位到新的无重复子串的起点应该在那里,此时应该还要多出一个变量来保存无重复字符串的起点,不妨设为left,那么i-left+1就是固定s[i]位置为终点情况下的,最长无重复子串。

注意:这里要警惕一个小小的坑,就是left虽然理论上是records[s[i]]+1,但是可能存在这样一种情况,在对s[a]重复剔除时,对s[a]之前的s[b]应该也剔除掉了,但是后面又出现了s[b]这个时候更新的left反而比之前更长了,区间长度计算就会出现错误,所以正确的解答方法应该是 left=max(records[s[i]]+1, left)

代码二

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if len(s) == 0:
            return 0
        
        records = {}
        left = 0
        res = 0
        i = 0
        n = len(s)
        while i < n:
            if s[i] in records:
                left = max(records[s[i]] + 1, left)
            records[s[i]] = i
            res = max(res, i - left + 1)
            i += 1
        return res

解法三:双指针+set删除元素时更新left指针

代码二的解法其实算的上很不错的了,但其实还可以继续优化,hash虽然对于index的查找很方便,但是其实也直接用容器删除元素来代替更新left。

思路:如果发现s[i]在records里面,那么将s[left]中元素一直从records中删除,left指针往右移,直到s[i]不存在records中,说明已经将s[i]以及s[i]之前的元素删除掉了,此时left就是下一个无重复子串的起点。

对这个容器,我们尽量需要它的删除接口尽可能的高效,虽然说存储无重复字符串用什么都可以,但是删除高效来说,还是用set性能更好。

代码三

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if len(s) == 0:
            return 0
        
        records = set()
        left = 0
        res = 0
        i = 0
        n = len(s)
        while i < n:
            while s[i] in records:
                records.remove(s[left])
                left += 1

            records.add(s[i])
            res = max(res, i - left + 1)
            i += 1
        return res

模板总结

看到力扣官方题解下面有人总结模板,想着也来总结一个python版本的。

left = 0 # 左边界
right = 0 # 右边界
n = len(arr) # 边界值
# 外层循环扩展外边界
while right < n:
    # 当前考虑的元素
    while (l <= r && check()): # 区间[left,right]不符合题意
       {{coding_block...}} # 扩展左边界
    
    # 区间[left,right]符合题意,统计相关信息
    {{coding_block...}}
    
    right += 1 # 右侧指针不断右移