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

示例 1 模拟:s = "abcabcbb"

表格

right char lastPos (初 - 1) left (更新) 窗口 [left, right] maxLen
0 a lastPos[97]=0 0 [0,0] 1
1 b lastPos[98]=1 0 [0,1] 2
2 c lastPos[99]=2 0 [0,2] 3
3 a lastPos[97]=3 max(0,0+1)=1 [1,3] 3
4 b lastPos[98]=4 max(1,1+1)=2 [2,4] 3
5 c lastPos[99]=5 max(2,2+1)=3 [3,5] 3
6 b lastPos[98]=6 max(3,3+1)=4 [4,6] 3
7 b lastPos[98]=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 也是有效位置。

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

相关推荐
_日拱一卒4 小时前
LeetCode:矩阵置零
java·数据结构·线性代数·算法·leetcode·职场和发展·矩阵
穿条秋裤到处跑4 小时前
每日一道leetcode(2026.04.10):三个相等元素之间的最小距离 I
算法·leetcode
nlpming4 小时前
OpenClaw 代码解析
算法
学习永无止境@4 小时前
MATLAB中矩阵转置
算法·matlab·fpga开发·矩阵
七颗糖很甜4 小时前
雨滴谱数据深度解析——从原始变量到科学产品的Python实现【下篇】
python·算法·pandas
nlpming4 小时前
OpenClaw system prompt定义
算法
nlpming4 小时前
OpenClaw安装配置及简介
算法
爱码小白4 小时前
MySQL 常用数据类型的系统总结
数据库·python·算法
玛丽莲茼蒿4 小时前
Leetcode hot100 【中等】括号生成
算法·leetcode·职场和发展