全栈开发知识库

31.无重复字符的最长子串

题目

给定一个字符串 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 由英文字母、数字、符号和空格组成

思路

这道题主要用到思路是:滑动窗口

什么是滑动窗口?

其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!

如何移动?

我们只要把队列的左边的元素移出就行了,直到满足题目要求!

一直维持这样的队列,找出队列出现最长的长度时候,求出解!

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        leftIndex = 0
        rightIndex = 0

        # pwwkew
        # "abcabcbb"
        result = 0
        while rightIndex < len(s):
            index = s[leftIndex: rightIndex].find(s[rightIndex])
            if index >= 0:
                leftIndex += index + 1
            rightIndex += 1
            result = max(result, rightIndex - leftIndex)
        return result

这个实现的时间复杂度是O(n²),显然不满足。

可以使用Set或者HashMap进行优化:

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        leftIndex = 0
        rightIndex = 0
        result = 0
        data = set()
        while rightIndex < len(s):
            while s[rightIndex] in data:
                data.remove(s[leftIndex])
                leftIndex += 1
            data.add(s[rightIndex])
            rightIndex += 1
            result = max(result, rightIndex - leftIndex)
        return result

class Solution {
    fun lengthOfLongestSubstring(s: String): Int {
        val window = HashSet<Char>()   // 窗口状态:当前窗口内的字符集合
        var left = 0
        var maxLen = 0

        for (right in s.indices) {
            val c = s[right]

            // 收缩条件:新字符已在窗口中 → 窗口非法 → 从左侧不断移出,直到重复消失
            while (c in window) {
                window.remove(s[left])
                left++
            }

            // 此时窗口已合法,纳入新字符
            window.add(c)

            // 窗口合法,更新最长长度(在 while 之后)
            maxLen = maxOf(maxLen, right - left + 1)
        }

        return maxLen
    }
}

方法一遇到重复时是一格一格移动 left。可以用 HashMap 记录每个字符最后出现的下标,遇到重复时让 left 一步跳到位,省去逐格移动。

class Solution {
    fun lengthOfLongestSubstring(s: String): Int {
        // 字符 -> 它最近一次出现的下标
        val lastIndex = HashMap<Char, Int>()
        var left = 0
        var maxLen = 0

        for (right in s.indices) {
            val c = s[right]

            // 如果 c 出现过,且上次出现位置在当前窗口内(>= left),
            // 直接把 left 跳到「上次出现位置的下一格」
            if (lastIndex.containsKey(c) && lastIndex[c]!! >= left) {
                left = lastIndex[c]!! + 1
            }

            lastIndex[c] = right                       // 更新该字符的最新位置
            maxLen = maxOf(maxLen, right - left + 1)
        }

        return maxLen
    }
}

  • 时间复杂度:O(n)


  • 邮箱 :charon.chui@gmail.com
  • Good Luck!