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

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

给定一个字符串 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)是该问题的最优下界,无法进一步优化,该解法已达到理论最优性能。
相关推荐
古茗前端团队1 小时前
急招!前端|测试|后端|产品(名额多,速来)
前端·后端·架构
喵个咪3 小时前
Go-Wind HTTP 服务器从入门到精通
后端·http·go
hunterandroid3 小时前
Hilt 依赖注入:从手动 new 到自动装配
后端
喵个咪3 小时前
Go-Wind gRPC 服务器从入门到精通
后端·go·grpc
喵个咪3 小时前
Go-Wind GraphQL 服务器从入门到精通
后端·graphql
青青子衿悠悠我心3 小时前
Docker与Kubernetes的十年战争与融合
后端
AI小老六3 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
云技纵横3 小时前
@Transactional 到底要不要加 rollbackFor?一次数据不一致事故讲清楚
后端·面试
Csvn3 小时前
日志分析进阶 — Logwatch 与 GoAccess 实战
后端