自学记录:力扣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 更简洁,适合入门理解。

相关推荐
Wilber的技术分享6 小时前
【机器学习实战笔记 12】集成学习:AdaBoost算法
人工智能·笔记·算法·决策树·机器学习·分类·集成学习
金融小师妹7 小时前
基于LSTM-GARCH混合模型的“获利了结”量化解析:黄金单日1.27%跌幅的技术性归因
大数据·人工智能·算法
不良手残8 小时前
Java实现10大经典排序算法
数据结构·算法·排序算法
是紫焅呢8 小时前
I排序算法.go
开发语言·后端·算法·golang·排序算法·学习方法·visual studio code
辉辉还没睡9 小时前
Lora训练
人工智能·算法·机器学习
电院大学僧11 小时前
初学python的我开始Leetcode题10-2
python·算法·leetcode
码破苍穹ovo12 小时前
二分查找----1.搜索插入位置
数据结构·算法
烨然若神人~13 小时前
算法第38天|322.零钱兑换\139. 单词拆分
算法
sukalot13 小时前
window显示驱动开发—输出合并器阶段
驱动开发·算法