每日算法练习: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 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 也是有效位置。

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

相关推荐
折哥的程序人生 · 物流技术专研15 小时前
Java面试85题图解版 · 特别篇:2026后端高频面试题复盘(算法底层逻辑+高并发架构设计全解析,附Java实战代码)
java·网络·数据库·算法·面试
想吃火锅100516 小时前
【leetcode】14.最长公共前缀js
算法·leetcode·职场和发展
云絮.17 小时前
数据库操作
数据库·mysql·算法·oracle
小林ixn17 小时前
LeetCode 206. 反转链表(迭代 + 递归详解)
算法·leetcode·链表
凡人叶枫18 小时前
Effective C++ 条款17:以独立语句将 newed 对象置入智能指针
java·linux·开发语言·c++·算法
我爱cope19 小时前
【Agent智能体26 | 多智能体-多智能体工作流】
人工智能·设计模式·语言模型·职场和发展
菜鸟‍19 小时前
LeetCode 1 27 和 704 || 两数之和 移除元素 二分查找
算法·leetcode·职场和发展
退休倒计时20 小时前
【每日一题】LeetCode 142. 环形链表 II TypeScript
算法·leetcode·链表·typescript
popcorn_min20 小时前
Digits 手写数字识别:随机森林多分类 + 像素级特征热力图
算法·随机森林·分类
liulilittle21 小时前
拥塞控制:排水终止的两种决策:OR 与 AND
网络·tcp/ip·计算机网络·算法·信息与通信·tcp·通信