自学记录:力扣hot100第三题

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

中等

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

一、滑动窗口思想详解

1. 基本概念
  • 窗口:一个连续的子区间,通过左右指针界定范围。
  • 滑动:窗口的左右边界可以向某个方向 "滑动",每次滑动一个单位。
  • 动态调整:根据当前窗口的状态,动态收缩左边界或扩展右边界。
2. 适用场景
  • 问题特征

    • 求满足条件的最长 / 最短子串
    • 子串需满足连续性(元素连续出现)。
    • 条件通常涉及元素不重复元素和 / 积等。
  • 典型问题

    • 无重复字符的最长子串(当前代码)。
    • 最小覆盖子串(LeetCode 76)。
    • 和为 K 的最长子数组(LeetCode 325)。

二、为什么使用滑动窗口?

1. 暴力解法的瓶颈

以 "无重复字符的最长子串" 为例,暴力解法需枚举所有子串(时间复杂度 O (n²)),并检查每个子串是否无重复(时间复杂度 O (n)),总时间复杂度为 O (n³)。

2. 滑动窗口的优化
  • 避免重复检查

    滑动窗口通过维护一个动态区间,利用历史信息快速判断新窗口是否合法。例如,当右边界扩展时,若发现重复字符,只需收缩左边界直至无重复,无需重新检查整个子串。

  • 时间复杂度降为 O (n)
    每个元素最多被左右指针各访问一次,总操作次数为 2n,时间复杂度 O (n)。

三、滑动窗口的核心逻辑

1. 窗口的维护
  • 扩展右边界

    不断将元素加入窗口,更新窗口状态(如字符频率、和等)。

  • 收缩左边界

    当窗口状态不满足条件时(如出现重复字符),收缩左边界并更新状态,直至条件满足。

2. 状态的高效更新
  • 数据结构辅助

    使用哈希表(HashSet/HashMap)记录窗口内元素的状态,支持 O (1) 时间的查询和更新。

  • 不变量的维护

    在窗口滑动过程中,某些属性(如元素和、不重复元素数)需动态维护,确保每次操作后状态的正确性。

java 复制代码
public int lengthOfLongestSubstring(String s) {
    int left = 0, right = 0;
    //定义一个哈希集合,因为HashSet不允许存储重复字符,可用来快速判断字符是否重复
    HashSet<Character> set = new HashSet<>();
    //初始化最长子串长度
    int maxLen = 0;
    //右指针遍历
    while (right < s.length()) { 
        // 当set里面有字符与右指针所指的s字符串中字符重复时,左指针开始滑动,收缩左边界
        while (set.contains(s.charAt(right))) {
            set.remove(s.charAt(left)); 
            left++; 
        }
        // 没重复时加入当前右指针所指s字符串的字符,扩展右边界
        set.add(s.charAt(right));
        right++;
        // 比较历史最长子串长度与当前set的大小,更新最大长度
        maxLen = Math.max(maxLen, set.size());
    }
    return maxLen;
}

​

学习时比较注意的知识点:

1. s.charAt(index) 是什么?

这是 Java 中 String 类的实例方法,用于获取字符串中指定位置的字符。

  • 作用 :返回字符串 s 中索引为 index 的字符(索引从 0 开始)。

  • 示例

    复制代码
    String s = "abc";
    char c = s.charAt(1);  // c 的值为 'b'(索引 1 对应第二个字符)
  • 在代码中的意义

    • s.charAt(right):获取右指针当前指向的字符。
    • s.charAt(left):获取左指针当前指向的字符。

2. 为什么要用 HashSet<Character>

这里的哈希表(HashSet)主要用于快速判断字符是否重复

  • 特性
    • 元素唯一性HashSet 不允许存储重复元素。
    • O (1) 查询效率:通过哈希函数直接定位元素,判断存在性的速度极快。
  • 在代码中的作用
    • 存储当前窗口的字符 :每次扩展右边界时,将字符加入 set
    • 检测重复 :通过 set.contains(...) 快速判断新字符是否已存在于窗口中。
    • 收缩窗口 :遇到重复字符时,通过 set.remove(...) 移除左指针字符,直到无重复。

对比其他数据结构

  • 若用数组遍历判断重复,时间复杂度为 O (n),会导致整体算法变为 O (n²)。
  • HashSet 的 O (1) 查询使算法保持 O (n) 高效。

3. Math.max(maxLen, set.size()) 如何工作?

这是 Java 中 Math 类的静态方法,用于比较两个值的大小并返回较大值。

  • 语法

    复制代码
    Math.max(a, b);  // 返回 a 和 b 中的较大值(a ≥ b ? a : b)
  • 在代码中的意义

    • set.size():当前窗口的大小(无重复字符的子串长度)。
    • maxLen:历史记录的最长子串长度。
    • 每次扩展窗口后,通过 Math.max 更新 maxLen,确保其始终记录最大值

示例

java 复制代码
// 假设当前窗口为 "abc",set.size() = 3,maxLen = 2
maxLen = Math.max(2, 3);  // maxLen 更新为 3

总结:三个关键设计的作用

代码元素 作用
s.charAt(index) 访问字符串中指定位置的字符,用于遍历和窗口边界操作。
HashSet<Character> 快速检测字符重复,确保窗口内无重复字符,是 O (n) 时间复杂度的核心。
Math.max(a, b) 动态更新最长子串长度,保证结果的正确性。

补充:为什么用 HashSet 而非 HashMap

在这个问题中,HashSet 已足够,因为只需存储字符本身,无需记录其位置。

若改用 HashMap<Character, Integer> 记录字符的最新位置,可进一步优化收缩逻辑(直接将左指针跳转到重复位置的下一位),但会增加代码复杂度。当前实现用 HashSet 更简洁,适合入门理解。

相关推荐
定偶几秒前
进制转换小题
c语言·开发语言·数据结构·算法
体系结构论文研讨会1 小时前
多项式环及Rq的含义
算法
智驱力人工智能1 小时前
极端高温下的智慧出行:危险检测与救援
人工智能·算法·安全·行为识别·智能巡航·高温预警·高温监测
森焱森1 小时前
60 美元玩转 Li-Fi —— 开源 OpenVLC 平台入门(附 BeagleBone Black 驱动简单解析)
c语言·单片机·算法·架构·开源
课堂剪切板2 小时前
ch07 题解
算法·深度优先
科大饭桶3 小时前
数据结构自学Day5--链表知识总结
数据结构·算法·leetcode·链表·c
我爱C编程5 小时前
基于Qlearning强化学习的1DoF机械臂运动控制系统matlab仿真
算法
chao_7895 小时前
CSS表达式——下篇【selenium】
css·python·selenium·算法
chao_7896 小时前
Selenium 自动化实战技巧【selenium】
自动化测试·selenium·算法·自动化
YuTaoShao6 小时前
【LeetCode 热题 100】24. 两两交换链表中的节点——(解法一)迭代+哨兵
java·算法·leetcode·链表