(LeetCode-Hot100)3. 无重复字符的最长子串

问题简介

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

📌 题目描述

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


示例说明

示例 1

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

示例 2

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

示例 3

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

示例 4

复制代码
输入: s = ""
输出: 0

解题思路

💡 核心思想 :使用滑动窗口(Sliding Window) + 哈希表(记录字符最近出现位置)

✅ 方法一:滑动窗口 + 哈希表(推荐)
  1. 使用两个指针 leftright 表示当前窗口的左右边界。
  2. 用一个哈希表 map 记录每个字符最近一次出现的下标
  3. 遍历字符串,right 指针不断右移:
    • 如果 s[right] 已在当前窗口中出现过(即 map 中存在且其索引 ≥ left),说明出现重复。
    • 此时将 left 移动到重复字符的下一个位置 (即 map[s[right]] + 1)。
  4. 更新 map[s[right]] = right
  5. 每次更新最大长度:maxLen = max(maxLen, right - left + 1)

❗注意:不能简单地将 left 设为 map[s[right]] + 1 而不判断是否在当前窗口内。例如 "abba",当 right=3(第二个 'a')时,map['a']=0,但此时 left=2(因为前面 'bb' 已经移动过),所以 'a' 不在当前窗口 [2,2] 中,不应移动 left

✅ 方法二:暴力法(不推荐,仅用于理解)
  • 枚举所有子串,对每个子串检查是否有重复字符。
  • 时间复杂度 O(n³),会超时。
✅ 方法三:优化的滑动窗口(使用数组代替哈希表)
  • 若字符集有限(如 ASCII 128 字符),可用 int[128] 数组代替 HashMap,初始化为 -1
  • 逻辑同方法一,但效率更高(常数时间更优)。

代码实现

tabs 复制代码
:::tab Java
class Solution {
    public int lengthOfLongestSubstring(String s) {
        if (s == null || s.length() == 0) return 0;
        
        Map<Character, Integer> map = new HashMap<>();
        int left = 0, maxLen = 0;
        
        for (int right = 0; right < s.length(); right++) {
            char c = s.charAt(right);
            // 如果字符已存在且在当前窗口内
            if (map.containsKey(c) && map.get(c) >= left) {
                left = map.get(c) + 1;
            }
            map.put(c, right);
            maxLen = Math.max(maxLen, right - left + 1);
        }
        
        return maxLen;
    }
}
:::
:::tab Go
func lengthOfLongestSubstring(s string) int {
    if len(s) == 0 {
        return 0
    }
    
    m := make(map[byte]int)
    left, maxLen := 0, 0
    
    for right := 0; right < len(s); right++ {
        c := s[right]
        if idx, ok := m[c]; ok && idx >= left {
            left = idx + 1
        }
        m[c] = right
        if right-left+1 > maxLen {
            maxLen = right - left + 1
        }
    }
    
    return maxLen
}
:::

💡 提示 :Go 中 mapok 返回值用于判断 key 是否存在。


示例演示(以 "pwwkew" 为例)

right char map 状态(char→index) left 当前窗口 maxLen
0 'p' {'p':0} 0 "p" 1
1 'w' {'p':0, 'w':1} 0 "pw" 2
2 'w' {'p':0, 'w':2} 2 "w" 2
3 'k' {'p':0, 'w':2, 'k':3} 2 "wk" 2
4 'e' ... 2 "wke" 3
5 'w' ... → 'w':5 3 "kew" 3

✅ 最终结果:3


答案有效性证明

  • 正确性 :滑动窗口始终维护一个无重复字符 的子串。
    • 当遇到重复字符时,left 跳到其上次出现位置之后,确保窗口内无重复。
    • 所有可能的无重复子串都会被窗口覆盖(因为 right 遍历所有位置)。
  • 最优性 :每次扩展 right 时都尝试更新最大长度,不会遗漏更优解。

复杂度分析

方法 时间复杂度 空间复杂度 说明
滑动窗口 + HashMap O(n) O(min(m, n)) m 是字符集大小(如 ASCII 128),n 是字符串长度
暴力法 O(n³) O(1) 枚举所有子串 + 检查重复
滑动窗口 + 数组 O(n) O(m) 更快的常数因子,适用于已知字符集

✅ 实际应用中,方法一(滑动窗口 + 哈希表) 是最优解。


问题总结

  • 🔑 关键技巧:滑动窗口 + 哈希表记录字符位置。
  • ⚠️ 易错点
    • 忘记判断重复字符是否在当前窗口内(如 "abba")。
    • 错误地将 left 直接设为 map[c] + 1 而不判断 map[c] >= left
  • 💡 扩展思考
    • 若求最长无重复子串本身(而非长度),只需额外记录起始位置和长度。
    • 若字符集很大(如 Unicode),仍可用 HashMap,空间复杂度为 O(k),k 为实际出现的不同字符数。

✅ 掌握此题,就掌握了滑动窗口类问题的核心思想!

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
南境十里·墨染春水1 天前
C++传记(面向对象)虚析构函数 纯虚函数 抽象类 final、override关键字
开发语言·c++·笔记·算法
无巧不成书02181 天前
30分钟入门Java:从历史到Hello World的小白指南
java·开发语言
2301_797172751 天前
基于C++的游戏引擎开发
开发语言·c++·算法
有为少年1 天前
告别“唯语料论”:用合成抽象数据为大模型开智
人工智能·深度学习·神经网络·算法·机器学习·大模型·预训练
比昨天多敲两行1 天前
C++ 二叉搜索树
开发语言·c++·算法
Season4501 天前
C++11之正则表达式使用指南--[正则表达式介绍]|[regex的常用函数等介绍]
c++·算法·正则表达式
Tisfy1 天前
LeetCode 2839.判断通过操作能否让字符串相等 I:if-else(两两判断)
算法·leetcode·字符串·题解
zs宝来了1 天前
Playwright 自动发布 CSDN 的完整实践
java
问好眼1 天前
《算法竞赛进阶指南》0x04 二分-1.最佳牛围栏
数据结构·c++·算法·二分·信息学奥赛
会编程的土豆1 天前
【数据结构与算法】优先队列
数据结构·算法