力扣解题-无重复字符的最长子串

力扣解题-无重复字符的最长子串

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

示例 1:

输入: s = "abcabcbb"

输出: 3

解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。

示例 2:

输入: s = "bbbbb"

输出: 1

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

示例 3:

输入: s = "pwwkew"

输出: 3

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

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

提示:

0 <= s.length <= 5 * 104

s 由英文字母、数字、符号和空格组成


第一次解答

解题思路

核心方法:滑动窗口(双指针) + 哈希集合去重,通过动态调整窗口的左右边界,在一次遍历中找到无重复字符的最长子串,将暴力解法的O(n²)时间复杂度优化至O(n),是该问题的经典最优解法。

具体步骤:

  1. 核心原理铺垫 :滑动窗口是解决"子串/子数组"类问题的常用技巧,本题中用leftright两个指针分别表示窗口的左、右边界,窗口[left, right]内始终维护一个"无重复字符的子串",通过动态调整边界来找到最长窗口长度。
  2. 初始化关键变量
    • seenHashSet<Character>集合,用于快速判断当前字符是否已在窗口内(O(1)查找效率),存储当前窗口内的所有字符;
    • left:窗口左指针,初始值为0,代表窗口起始位置;
    • maxLength:记录遍历过程中找到的最长无重复子串长度,初始值为0。
  3. 右指针遍历扩展窗口
    • 右指针right从0开始遍历字符串的每个字符c = s.charAt(right),代表尝试将当前字符加入窗口;
    • 若当前字符c已存在于seen集合中(说明窗口内有重复字符),则进入循环收缩左边界:
      • 移除左指针left指向的字符(seen.remove(s.charAt(left)));
      • 左指针left右移一位(left++);
      • 重复上述操作,直到seen中不再包含c(此时窗口内恢复无重复状态)。
    • 将当前字符c加入seen集合(此时c是窗口新的右边界字符);
    • 计算当前窗口的长度(right - left + 1),并更新maxLength为"当前最大值"和"窗口长度"中的较大者。
  4. 返回结果 :遍历完成后,maxLength即为无重复字符的最长子串长度,直接返回。

核心优化逻辑说明

  1. 时间复杂度优化
    • 暴力解法需枚举所有子串(O(n²)),并对每个子串检查是否有重复(O(n)),总时间复杂度O(n³),无法适配n=5×10⁴的规模;
    • 滑动窗口仅需一次遍历(右指针遍历n次),左指针最多也移动n次,整体时间复杂度为O(n)(每个字符最多被左/右指针各访问一次),完全满足题目性能要求。
  2. 空间复杂度说明
    • 该解法用HashSet存储窗口内字符,最坏情况下(字符串无重复)需存储n个字符,空间复杂度为O(n),这是"时间换空间"的合理权衡;
    • 若需进一步优化空间,可改用数组(ASCII字符范围)替代HashSet,但核心逻辑不变,仅存储方式不同。
  3. 性能表现说明:
    • 耗时5ms击败32.91%的用户:滑动窗口是该题的标准最优解,多数提交均采用此逻辑,耗时差异主要来自评测机运行环境(如JVM的即时编译、系统资源占用等),该耗时已属于高效范畴;
    • 内存消耗76MB击败19.06%的用户:核心原因是HashSet的底层存储(如HashMap的数组+链表/红黑树结构)存在额外内存开销,若改用数组存储字符是否存在,可降低内存消耗。

执行耗时:6 ms,击败了61.68% 的Java用户

内存消耗:45.9 MB,击败了13.31% 的Java用户

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

        for (int right = 0; right < s.length(); right++) {
            char c = s.charAt(right);
            while (seen.contains(c)) {
                seen.remove(s.charAt(left));
                left++;
            }
            seen.add(c);
            maxLength = Math.max(maxLength, right - left + 1);
        }
        return maxLength;
    }

总结

  1. 该解法的核心是滑动窗口动态维护无重复子串:右指针扩展窗口,左指针收缩窗口以消除重复,确保窗口内始终无重复字符,一次遍历即可找到最长长度;
  2. HashSet的O(1)查找效率是滑动窗口能高效运行的关键,避免了每次检查重复都遍历窗口的冗余操作;
  3. 滑动窗口的时间复杂度O(n)是该问题的最优下界,无法进一步优化,该解法已达到理论最优性能。
相关推荐
W133309089071 小时前
大专应用统计学专业,怎么区分数据统计岗和数据分析岗?
人工智能·算法·数据分析
羑悻的小杀马特1 小时前
LFU缓存算法全解:从双哈希+双向链表到O(1)艺术,解锁长期热点守护神
算法·缓存·哈希算法·lfu·双链表
kebijuelun1 小时前
GLM-5:从 Vibe Coding 走向 Agentic Engineering 的全栈路线图
人工智能·深度学习·算法·语言模型
@insist1231 小时前
软考-软件设计师-数据表示核心考点详解:从进制转换到 IEEE 754 标准
java·数据结构·算法
码luffyliu1 小时前
Go 微服务 RPC 实践:从 IDL 定义到 SDK 调用的完整链路
后端·微服务·rpc·golang
NGC_66111 小时前
【无标题】
数据结构·算法·排序算法
NGC_66111 小时前
快速排序算法
数据结构·算法·排序算法
机器视觉的发动机2 小时前
图像处理-机器视觉算法中的数学基础
开发语言·人工智能·算法·决策树·机器学习·视觉检测·机器视觉
Darkwanderor2 小时前
离散化思维的应用
数据结构·c++·算法·哈希算法