【LeetCode 热题 100】无重复字符的最长子串


精选专栏链接 🔗


欢迎订阅,点赞+关注,每日精进1%,与百万开发者共攀技术珠峰

更多内容持续更新中~



【LeetCode 热题 100】无重复字符的最长子串

📝题目描述

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

示例 1:

bash 复制代码
输入: s = "abcabcbb"
输出: 3 

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

注意 "bca" 和 "cab" 也是正确答案。

示例 2:

bash 复制代码
输入: s = "bbbbb"
输出: 1

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

示例 3:

bash 复制代码
输入: s = "pwwkew"
输出: 3

解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。

请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。


💡提示信息

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

关键概念区分

在动手写代码前,必须明确两个概念的区别,这也是容易踩坑的地方:

  • 子串 :必须是原字符串中 连续 的一段。例如 "abc" 是 "abcde" 的子串;
  • 子序列:不需要连续,只要保持相对顺序即可。例如 "ace" 是 "abcde" 的子序列;

注意:本题明确要求的是 子串。这意味着我们不能跳着选字符,必须在一段连续的区间内寻找答案。


解题思路分析

为了消除重复计算,我们引入 滑动窗口 的思想。

算法核心思想

维护一个窗口 [left, right],保证窗口内的字符 始终是不重复的

  • 右指针 (right):主动向右移动,负责"探索"新字符,扩大窗口;
  • 左指针 (left):被动向右移动,负责"收缩"窗口。只有当窗口内出现重复字符时,才移动左指针,直到把那个导致重复的旧字符移出窗口;

效果演示

以字符串 s = "abcabcbb" 为例:

text 复制代码
步骤 1: [a] b c a b c b b   -> 窗口 {a}, 长度 1
步骤 2: [a b] c a b c b b   -> 窗口 {a,b}, 长度 2
步骤 3: [a b c] a b c b b   -> 窗口 {a,b,c}, 长度 3 (目前最大)
步骤 4:  a [b c a] b c b b  -> 遇到 'a' 重复! 左指针右移,移除旧的 'a'。窗口 {b,c,a}, 长度 3
步骤 5:  a b [c a b] c b b  -> 遇到 'b' 重复! 左指针右移,移除旧的 'b'。窗口 {c,a,b}, 长度 3
...以此类推

通过这种方式,左右指针各最多遍历一次字符串,时间复杂度仅为 O ( n ) O(n) O(n)。


方法一:HashSet + 双指针

此方法这是最标准的滑动窗口模板。具体来说是使用 HashSet 来存储当前窗口内的字符,利用集合的特性快速判断是否重复

算法流程如下:

  • 使用一个 HashSet 记录当前窗口里有哪些字符;
  • 右指针 right 不断向右走,把新字符加进来;
  • 若新字符已经在 Set 里了(说明重复了),我们就让 左指针 (left) 一步一步 向右挪,并从 Set 中移除对应的字符,直到把那个导致重复的旧字符"挤"出去为止。

Java 代码实现如下:

java 复制代码
class Solution {
    public int lengthOfLongestSubstring(String s) {
        HashSet<Character> window = new HashSet<>();  // HashSet存储当前窗口内的字符
        int left = 0;
        int maxLen = 0;

        for(int right = 0; right < s.length(); right++){
            char c = s.charAt(right);

            // 如果发生冲突,左指针一步步右移,直到移除重复的那个字符
            while(window.contains(c)){
                window.remove(s.charAt(left));
                left++;
            }

            // 此时窗口内无重复,加入新字符并更新最大长度
            window.add(c);
            maxLen = Math.max(maxLen, right - left + 1);
        }


        return maxLen;
    }
}

提交代码,运行结果如下:


方法二:HashMap 优化

在方法一中,当遇到重复字符时,左指针是一步步挪动的。如果我们用 HashMap 记录每个字符 最后一次出现的索引,左指针就可以直接"跳跃"到重复字符的下一位,减少无效操作。

算法流程如下:

  • HashMap 不仅记录字符本身(Key),还记录该字符 最后一次出现的下标(Value);
  • 右指针 向右遍历时,如果发现当前字符在 Map 中存在;
  • 我们不需要一步步挪左指针,而是直接查看该字符上次出现的位置;
  • 将 左指针直接"跳跃" 到 上次出现位置 + 1 的地方(当然,要保证左指针不能回退)。这样就瞬间消除了重复。

说明:

HashMap Key 唯一且不可重复。 在滑动窗口向右推进的过程中,同一个字符可能会多次出现。因为 HashMap 的 Key 是唯一的,当我们每次调用 map.put(c, right) 时,如果这个字符 c 之前已经存在,新的索引 right 就会直接覆盖掉旧的索引。

Java代码实现如下:

java 复制代码
import java.util.HashMap;
import java.util.Map;

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> map = new HashMap<>(); // Key存字符,Value存该字符最后出现的索引
        int left = 0;
        int 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;
    }
}

提交代码,运行结果如下:

优化后的效率要比方法一快接近一倍。


总结

这道题是学习 滑动窗口 算法的经典入门题。

  1. 识别特征:当你看到求"最长/最短子串/子数组"且满足某种条件(如无重复、和为K)时,首先考虑滑动窗口。
  2. 核心难点 :在于确定何时移动左指针。
    • 在本题中,条件是"无重复",所以当 set.contains(nextChar) 为真时,必须收缩左边界。
  3. 避坑指南 :在使用 HashMap 优化时(方法二),务必注意 left = Math.max(left, ...) 这一步。这是因为左指针只能向右走,不能回头(例如处理 "abba" 这种回文结构时,如果不取 max,left 可能会倒退)。

希望这篇博客能帮你彻底搞懂这道题!

相关推荐
Dlrb12111 小时前
数据结构-排序算法
数据结构·算法·排序算法·插入排序·堆排序·希尔排序·快速排序
Yeats_Liao1 小时前
好复杂的 IoT 世界:工业数据采集技术栈全景解析
java·物联网·struts
月落归舟1 小时前
Java线程小记
java·开发语言
西凉的悲伤1 小时前
Spring Cloud Gateway介绍
java·spring cloud·gateway
逸Y 仙X2 小时前
文章五:Elasticsearch安全通信
java·大数据·安全·elasticsearch·搜索引擎·全文检索·jenkins
quan26312 小时前
20260529,日常开发-查老数据全量更新闭坑
java·mysql·主从·主从延迟
大大杰哥2 小时前
Java 日志框架详解:SLF4J + Logback 从入门到实战
java·开发语言·logback
Raink老师2 小时前
【AI面试临阵磨枪-088】Skill 如何做参数校验、依赖注入、权限控制、超时、重试、幂等?
人工智能·面试·职场和发展
莫等闲-2 小时前
leetcode42. 接雨水 leetcode84.柱状图中最大的矩形
数据结构·c++·算法·leetcode