每日算法练习:LeetCode 3. 无重复字符的最长子串 ✅

大家好,我是你们的算法小伙伴。今天我们来练习一道字符串与滑动窗口的经典经典题 ------LeetCode 3. 无重复字符的最长子串。这道题是面试中考察滑动窗口(双指针)和哈希表的标杆题目,核心在于如何用线性时间复杂度动态维护一个 "无重复字符的窗口"。


题目描述

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

注意:子串是连续的字符序列,不同于子序列(不要求连续)。


示例 1:

复制代码
输入:s = "abcabcbb"

输出:3

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

示例 2:

复制代码
输入:s = "bbbbb"

输出:1

解释:因为无重复字符的最长子串是 "b"。

示例 3:

复制代码
输入:s = "pwwkew"

输出:3

解释:因为无重复字符的最长子串是 "wke",注意 "pwke" 是子序列而不是子串。

提示:

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

解题思路

核心矛盾

如何在遍历字符串的过程中,快速判断当前字符是否重复,并快速调整窗口左边界?

方法一:滑动窗口 + 哈希表(最优解,O (n))

核心思想 :用左右指针维护一个动态窗口 [left, right],保证窗口内字符唯一。

  1. 右指针:不断向右扩展,将新字符加入窗口。
  2. 哈希表(数组):记录每个字符最后一次出现的索引。
  3. 左指针调整:当右指针遇到重复字符时,将左指针直接跳转到 "重复字符的下一位"(需取当前左指针与历史位置的较大值,防止回退)。
  4. 更新长度:每次遍历都计算当前窗口长度,更新最大值。

为什么用数组? ASCII 码表共有 128 个字符,因此可以用一个长度为 128 的 int 数组代替 HashMap,将时间复杂度优化到极致,且空间复杂度仅为 O(1)。


代码实现

方法一:滑动窗口(最优解)

复制代码
class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        int maxLen = 0;
        // 定义数组记录字符最后出现的位置,初始为-1
        int[] lastPos = new int[128];
        Arrays.fill(lastPos, -1);
        
        int left = 0; // 窗口左边界
        
        // 右指针遍历字符串
        for (int right = 0; right < n; right++) {
            char c = s.charAt(right);
            // 关键步骤:如果当前字符出现过,且其位置在窗口内,更新左边界
            // Math.max是为了防止left回退(例如"abba"这种情况)
            left = Math.max(left, lastPos[c] + 1);
            
            // 更新当前字符的最新位置
            lastPos[c] = right;
            
            // 计算当前窗口长度并更新最大值
            maxLen = Math.max(maxLen, right - left + 1);
        }
        
        return maxLen;
    }
}

代码详解

1. 初始化

  • lastPos[128]:存储每个 ASCII 字符最后一次出现的索引,初始化为 -1(表示未出现)。
  • left = 0:窗口起始位置。
  • maxLen = 0:记录最长子串长度。

2. 遍历逻辑(右指针扩展)

  • char c = s.charAt(right):获取当前字符。
  • left = Math.max(left, lastPos[c] + 1):核心去重逻辑 。
    • 如果 c 是第一次出现,则 lastPos[c] = -1,left 保持不变。
    • 如果 c 重复出现,则将左边界移动到该字符上一次出现位置的下一位,确保窗口内无重复。
    • 使用 Math.max 是为了处理如 "abba" 这种情况:当处理到第二个 'a' 时,lastPos['a'] 是 0,left 会变成 1;但当处理第二个 'b' 时,lastPos['b'] 是 2,如果不加 max,left 会回退到 3,这是错误的,因此必须保证 left 只增不减。
  • lastPos[c] = right:更新该字符的最新位置。
  • maxLen = ...:计算窗口长度 right - left + 1 并取最大值。

示例 1 模拟:s = "abcabcbb"

表格

right char lastPos (初 - 1) left (更新) 窗口 left, right maxLen
0 a lastPos97=0 0 0,0 1
1 b lastPos98=1 0 0,1 2
2 c lastPos99=2 0 0,2 3
3 a lastPos97=3 max(0,0+1)=1 1,3 3
4 b lastPos98=4 max(1,1+1)=2 2,4 3
5 c lastPos99=5 max(2,2+1)=3 3,5 3
6 b lastPos98=6 max(3,3+1)=4 4,6 3
7 b lastPos98=7 max(4,4+1)=5 5,7 3
最终结果为 3。

复杂度分析

表格

解法 时间复杂度 空间复杂度 优点
滑动窗口 + 数组 O(n) O(1) 每个字符仅遍历一次,常数级空间,效率最高
暴力枚举 O(n3) O(1) 思路直观,但绝对超时,不推荐
滑动窗口 + HashMap O(n) O(k) (k 为字符集大小) 通用性强,但对于本题数组更快

总结

  1. 核心考点 :本题是滑动窗口的经典应用,考察对 "双指针动态调整" 和 "重复字符快速定位" 的理解。
  2. 优化技巧 :使用 int[128] 数组代替 HashMap,是处理 ASCII 字符集问题的标准技巧,能显著提升运行效率。
  3. 易错点 :
    • left 的更新必须用 Math.max:这是最容易出错的地方,必须保证左指针只向右移动,不向左回退。
    • 窗口长度计算 :right - left + 1,不要忘记加 1。
    • 初始值设定:数组初始化为 -1 而非 0,因为索引 0 也是有效位置。

今天的每日算法练习就到这里,我们明天再见!👋

相关推荐
倒头就睡的小比特3 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼3 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
旖旎夜光3 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark3 天前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her13 天前
并查集-听课笔记
笔记·算法·并查集
码流子3 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven3 天前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark3 天前
从算法设计模式看编程思维的抽象能力4
算法
2601_962218613 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法