(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

相关推荐
Lenyiin2 小时前
《LeetCode 顺序刷题》11 -20
java·c++·python·算法·leetcode·lenyiin
wuqingshun3141592 小时前
说一下java的四种引用
java·开发语言
青春:一叶知秋2 小时前
【Redis存储】Redis客户端
java·数据库·redis
乌萨奇也要立志学C++2 小时前
【洛谷】从记忆化搜索到动态规划 状态表示 + 转移方程 + 空间优化全攻略
算法·动态规划
curry____3032 小时前
c++位运算符笔记
java·c++·笔记
Hx_Ma163 小时前
测试题(一)
java
w***29853 小时前
Knife4j文档请求异常(基于SpringBoot3,查找原因并解决)
java·服务器·数据库
Bear on Toilet3 小时前
递归_二叉树_48 . 二叉树最近公共祖先查找
数据结构·算法·二叉树·dfs
yaoxin5211234 小时前
325. Java Stream API - 理解 Collector 的三大特性:助力流处理优化
java·开发语言