【LeetCode01】—— 无重复字符的最长子串:滑动窗口经典题详解

【LeetCode】------ 无重复字符的最长子串:滑动窗口经典题详解

导语

LeetCode 第 3 题"无重复字符的最长子串"(Longest Substring Without Repeating Characters)是滑动窗口算法的入门经典。这道题在面试中出现频率极高,不仅考察你对字符串处理的理解,更是检验你是否真正掌握滑动窗口思想的试金石。

很多初学者拿到这题会想到暴力枚举所有子串,但那样做的时间复杂度是 O(n³),在 LeetCode 上直接超时。用滑动窗口可以做到 O(n),这就是本文要讲的核心。


一、题目描述

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

示例 1:

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

示例 2:

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

示例 3:

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

约束条件:

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

二、核心概念:什么是滑动窗口

滑动窗口本质上是双指针的一种应用。维护一个窗口 [left, right],窗口内的元素满足某个条件(这里是"无重复字符")。通过移动左右边界来探索所有可能的合法窗口,同时记录最优解。

说白了,就是用两个指针框住一段字符串,右边往右扩直到不满足条件,左边往右缩来恢复条件,过程中不断更新答案。

本章小结

概念 说明
窗口 leftright 两个指针界定的子串
窗口扩张 right 右移,尝试纳入新字符
窗口收缩 left 右移,移除左侧字符以消除重复
合法条件 窗口内无重复字符
最优解 所有合法窗口中的最大长度

三、解法一:暴力枚举(理解用,实际不推荐)

暴力思路:枚举所有子串,检查每个子串是否无重复,取最长的。

python 复制代码
def lengthOfLongestSubstring(s: str) -> int:
    def all_unique(sub: str) -> bool:
        return len(set(sub)) == len(sub)

    max_len = 0
    for i in range(len(s)):
        for j in range(i + 1, len(s) + 1):
            if all_unique(s[i:j]):
                max_len = max(max_len, j - i)
    return max_len

复杂度分析:

  • 时间:O(n³) --- 枚举子串 O(n²),检查是否重复 O(n)
  • 空间:O(min(n, m)),m 为字符集大小

这个解法在 LeetCode 上会 TLE(超时),放在这里只是为了让你理解问题的朴素形式。


四、解法二:滑动窗口 + 哈希集合(标准解法)

核心思路:

  1. leftright 两个指针维护一个窗口
  2. 用一个 Set 记录窗口内已有字符
  3. right 不断右移,如果 s[right] 不在集合中,加入集合,更新最大长度
  4. 如果 s[right] 已在集合中(重复了),就不断右移 left 并从集合中移除 s[left],直到重复消失
  5. 重复 3-4 直到 right 遍历完整个字符串

图解过程

s = "abcabcbb" 为例:

复制代码
步骤   窗口          集合           max_len
1      [a]           {a}            1
2      [ab]          {a,b}          2
3      [abc]         {a,b,c}        3
4      [abca]b       重复! 左移 →
       a[bca]b       {b,c,a}        3
5      ...继续推进...

代码实现(Python)

python 复制代码
def lengthOfLongestSubstring(s: str) -> int:
    char_set = set()
    left = 0
    max_len = 0

    for right in range(len(s)):
        # 如果当前字符已在窗口中,收缩左边界
        while s[right] in char_set:
            char_set.remove(s[left])
            left += 1
        # 当前字符加入窗口
        char_set.add(s[right])
        # 更新最大长度
        max_len = max(max_len, right - left + 1)

    return max_len

代码实现(Java)

java 复制代码
public int lengthOfLongestSubstring(String s) {
    Set<Character> charSet = new HashSet<>();
    int left = 0, maxLen = 0;

    for (int right = 0; right < s.length(); right++) {
        while (charSet.contains(s.charAt(right))) {
            charSet.remove(s.charAt(left));
            left++;
        }
        charSet.add(s.charAt(right));
        maxLen = Math.max(maxLen, right - left + 1);
    }

    return maxLen;
}

代码实现(Go)

go 复制代码
func lengthOfLongestSubstring(s string) int {
    charSet := make(map[byte]bool)
    left, maxLen := 0, 0

    for right := 0; right < len(s); right++ {
        for charSet[s[right]] {
            delete(charSet, s[left])
            left++
        }
        charSet[s[right]] = true
        if right-left+1 > maxLen {
            maxLen = right - left + 1
        }
    }

    return maxLen
}

复杂度分析:

  • 时间:O(n) --- 虽然有内层 while 循环,但每个字符最多被 left 扫过一次,总体是 O(2n) = O(n)
  • 空间:O(min(n, m)),m 为字符集大小

五、解法三:滑动窗口 + 哈希表(优化版)

上面的解法中,当发现重复时,left 需要一步步右移。有没有办法直接跳到重复字符的下一个位置?

用哈希表记录每个字符最后出现的位置。当发现 s[right] 重复时,直接把 left 跳到 重复位置 + 1,省去中间的逐步收缩。

代码实现(Python)

python 复制代码
def lengthOfLongestSubstring(s: str) -> int:
    char_index = {}  # 字符 -> 最后出现的索引
    left = 0
    max_len = 0

    for right in range(len(s)):
        char = s[right]
        # 如果字符重复且在当前窗口内,直接跳到重复位置+1
        if char in char_index and char_index[char] >= left:
            left = char_index[char] + 1
        char_index[char] = right
        max_len = max(max_len, right - left + 1)

    return max_len

关键点:char_index[char] >= left

这个条件很重要。如果记录的重复位置在 left 左边,说明那个重复字符已经不在当前窗口里了,不需要跳。

代码实现(Java)

java 复制代码
public int lengthOfLongestSubstring(String s) {
    Map<Character, Integer> charIndex = new HashMap<>();
    int left = 0, maxLen = 0;

    for (int right = 0; right < s.length(); right++) {
        char c = s.charAt(right);
        if (charIndex.containsKey(c) && charIndex.get(c) >= left) {
            left = charIndex.get(c) + 1;
        }
        charIndex.put(c, right);
        maxLen = Math.max(maxLen, right - left + 1);
    }

    return maxLen;
}

复杂度分析:

  • 时间:O(n) --- 只需一次遍历
  • 空间:O(min(n, m))

六、三种解法对比

解法 时间复杂度 空间复杂度 特点
暴力枚举 O(n³) O(min(n,m)) 理解用,会超时
滑动窗口+集合 O(n) O(min(n,m)) 标准解法,容易理解
滑动窗口+哈希表 O(n) O(min(n,m)) 优化版,减少不必要的收缩

实际面试中,写解法二就够了。解法三是锦上添花,能体现你对滑动窗口的深入理解。


七、常见问题与避坑

1. 为什么 left 可以直接跳而不是逐步收缩?

因为在哈希表优化版中,我们记录了每个字符的最后位置。当发现重复时,我们确定重复字符在 char_index[char] 这个位置,直接跳到它的下一位就行。中间那些字符肯定不重复(否则之前就会被处���掉),所以可以安全跳过。

2. 为什么判断条件是 char_index[char] >= left

举个例子:s = "abba"

  • 处理到第二个 b 时,left = 0char_index['b'] = 1,需要跳到 left = 2
  • 处理到最后一个 a 时,char_index['a'] = 0,但 left = 20 < 2,说明第一个 a 已经不在窗口内了,不应该跳

不加这个条件,结果会错。

3. 空字符串怎么处理?

题目约束 0 <= s.length,所以空串是合法输入。上述代码对空串自然返回 0,不需要特殊处理。

4. 全是相同字符怎么办?

比如 s = "bbbb",窗口会一直保持长度 1,leftright 同步右移,最终返回 1。没问题。

5. 字符集只有 ASCII 吗?

题目说"英文字母、数字、符号和空格",但实际代码对 Unicode 也适用,因为哈希表/集合可以存任意字符。


八、滑动窗口的通用模式

这道题的解法可以抽象为滑动窗口的通用模板:

python 复制代码
def sliding_window(s):
    window = {}  # 或 set,视题目而定
    left = 0
    result = 0

    for right in range(len(s)):
        # 1. 扩大窗口
        c = s[right]
        window[c] = window.get(c, 0) + 1

        # 2. 收缩窗口(当窗口不满足条件时)
        while 窗口需要收缩:
            d = s[left]
            window[d] -= 1
            left += 1

        # 3. 更新结果
        result = max(result, right - left + 1)

    return result

掌握这个模板后,很多子串/子数组问题都能套用,比如:

  • LeetCode 76:最小覆盖子串
  • LeetCode 438:找到字符串中所有字母异位词
  • LeetCode 209:长度最小的子数组

九、总结

核心要点:

  1. 滑动窗口是解决"最长/最短子串/子数组"问题的利器,时间复杂度 O(n)
  2. 用集合记录窗口内容判断重复,用哈希表可以进一步优化跳跃
  3. left 跳跃时要检查 char_index[char] >= left,避免跳出窗口边界
  4. 滑动窗口的套路是固定的:扩大 → 收缩 → 更新结果

验证清单:

  • 能手写滑动窗口 + 集合的标准解法
  • 理解哈希表优化版中 >= left 条件的作用
  • 能用通用模板解决类似的子串问题
  • 能分析时间复杂度并解释为什么是 O(n)

参考资源

相关推荐
zhiSiBuYu05176 分钟前
Python3 循环语句新手实战指南
python
ShirleyWang0127 分钟前
Day02 K3s NGF(Nginx Gateway Fabric)单 Worker 环境网关更新与故障处置 SOP
linux·服务器·python·k8s·k3s
ZDQNFU7 分钟前
ORM之SQLAlchemy教程
后端·python
微露清风7 分钟前
模拟算法学习记录
学习·算法
jeffzhengye15 分钟前
Claude-Real-Video:让 Claude 真正看懂视频,0 Token 成本
python·微信
IT知识分享22 分钟前
WebP转JPG开发经验:从格式解码到本地批量转换的实践
javascript·python·图片转换
小柯南敲键盘35 分钟前
Temu多语言商品图翻译实现方案
人工智能·python
问商十三载43 分钟前
2026法律行业AI引擎生成式优化怎么优化?三层专业加固提排名,零成本提33%引用率附适配表
java·前端·人工智能·算法
zander2581 小时前
138. 随机链表的复制
数据结构·算法·链表
威联通安全存储1 小时前
TS-h1677AXU-RP在工程机械机器人弧焊中的部署
大数据·人工智能·python·机器人