当前位置: 首页 > news >正文

【滑动窗口专题】第二讲:无重复字符的最长子串

🌿 滑动窗口去重,右扩左缩,轻盈地捕获最长无重复子串!

🌱 文章摘要

暴力枚举到滑动窗口优化,本篇带你掌握“窗口去重”的核心技巧,快速求解最长无重复子串问题。

通过灵活地右扩覆盖、左缩去重,我们能在 O(n) 时间内高效求解,彻底告别 O(n²) 的低效暴力法。这一题,是掌握字符串滑动窗口的必经之路!


🧭 导读

在上一篇《长度最小的子数组》中,我们初次体验了滑动窗口的节奏:右扩 → 满足条件 → 左缩 → 优化结果
这一讲,我们将把这套节奏用在字符串问题上,进入滑动窗口的另一个核心方向——窗口去重

👉 如果你还没有阅读过我的《双指针专题》,强烈建议从那里起航

👉 双指针专题_明天会有多晴朗的博客-CSDN博客
“移动零”到“四数之和”,你会更好地理解为什么滑动窗口是双指针思想的自然延伸


一、题目描述

无重复字符的最长子串

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

示例 1:

输入:s = "abcabcbb"
输出:3
解释:最长子串是 "abc",长度为 3。

示例 2:

输入:s = "bbbbb"
输出:1
解释:最长子串是 "b"。

示例 3:

输入:s = "pwwkew"
输出:3
解释:最长子串是 "wke"。注意 "pwke" 是子序列,不是子串。

二、思路引导:窗口去重的伸缩技巧

1. 暴力法(直觉,但低效)

  • 遍历所有起点 i,从每个起点向后扩张,直到遇到重复字符

  • 每次扩张时使用一个哈希表来检测是否出现重复字符。

  • 不断更新最大长度。

缺点很明显:

  • 对于长度为 n 的字符串,需要 O(n²) 的枚举

  • 每次都要清空哈希结构,性能差;

  • 当 n 达到 5×10⁴ 时,轻松超时。

// 暴力解法 O(n^2)
int lengthOfLongestSubstring(string s) {int ret = 0, n = s.size();for (int i = 0; i < n; i++) {int hash[128] = {0};for (int j = i; j < n; j++) {if (++hash[s[j]] > 1) break;ret = max(ret, j - i + 1);}}return ret;
}

2. 滑动窗口法(高效,优雅)

关键观察:

  • 窗口内不允许出现重复字符;

  • 当右指针右移加入一个字符时,如果该字符已经在窗口中,就必须收缩左指针,直到它不重复为止;

  • 这样,窗口始终保持「无重复」性质。

具体做法:

  1. leftright 表示当前窗口;

  2. right 不断右移,将字符加入窗口;

  3. 如果发现当前字符出现次数 > 1,则说明重复,开始移动 left,逐个移除左侧字符,直到重复被消除;

  4. 在每次合法窗口下,更新最大长度 ans


三、窗口变化示意

字符串:abcabcbb

Step 1: [a] → max = 1
Step 2: [ab] → max = 2
Step 3: [abc] → max = 3
Step 4: [abca] → a 重复 → 左移,直到去掉第一个 a → [bca]
Step 5: [bcab] → b 重复 → 左移,直到去掉第一个 b → [cab]
...

关键:当右扩导致重复,就用左缩去除重复,保持窗口合法


四、算法流程

1. 初始化

  • 准备数组 hash[128] 统计字符出现次数(也可以用 map)。

  • 初始化左右指针 left = 0, right = 0,答案 ans = 0

2. 右扩

  • 每次把 s[right] 纳入窗口,hash[s[right]]++

  • right++

3. 左缩

  • hash[s[right]] > 1 时,说明当前字符重复:

    不断移动 left,对每个字符 hash[s[left]]--,直到当前重复字符次数降为 1。

4. 更新结果

  • 每次窗口合法时更新 ans = max(ans, right - left)

遍历结束返回 ans


五、代码实现(C++)

#include <string>
#include <algorithm>
using namespace std;class Solution {
public:int lengthOfLongestSubstring(string s) {int hash[128] = {0};      // 字符频次表(ASCII)int left = 0, right = 0;  // 滑动窗口左右指针int n = s.size();int ans = 0;while (right < n) {hash[s[right]]++; // 右扩:加入一个字符// 出现重复 → 左缩while (hash[s[right]] > 1) {hash[s[left]]--;left++;}// 窗口合法时更新最大长度ans = max(ans, right - left + 1);right++;}return ans;}
};

六、代码剖析与细节问题

1. 为什么用 while 而不是 if

因为可能出现多个相同字符连在一起,比如 "abba"。如果只判断一次,窗口可能还不合法;而 while 会一直收缩直到合法。

2. hash 数组大小 128 的原因

题目限定输入字符是英文或符号,ASCII 范围足够,用数组代替 map 速度更快,空间 O(1)。

3. 更新 ans 的位置很重要

必须在窗口合法之后更新,即完成收缩后。

4. 暴力 vs 滑动窗口

  • 暴力每次都从头统计 → O(n²);

  • 滑动窗口左右指针各最多走 n 步 → O(n)。


七、复杂度分析

  • 时间复杂度:O(n)

虽然有嵌套 while,但 leftright 各自最多移动 n 次,总共 2n,摊还线性。

  • 空间复杂度:O(1)

仅使用固定大小的 hash 数组,不依赖额外数据结构。


八、总结

这道题是滑动窗口中最经典的「窗口去重」问题:

  • 右扩增加长度,左缩去重保持合法;

  • 维护一个哈希频次表是关键;

  • 时间复杂度从 O(n²) → O(n),性能飞跃。

掌握这个模式,你就能自如地应对后续字符串窗口题,例如「最小覆盖子串」「字符串的排列」等进阶题。


🔜 下一讲预告

下一篇我们将进入《最大连续 1 的个数 III》

当窗口中允许有限次“容错”时,如何用滑动窗口保持最大连续段?这将是对“窗口状态维护”的一次小进阶 👣


📚 系列更新提示

本文是《滑动窗口专题》第 2 篇
本专题将带你从最基础的“最短子数组”,一路深入到「最小覆盖子串」「滑动窗口最大值」等经典高频题,系统掌握滑动窗口在数组与字符串问题中的全部核心用法。


📌 系列持续更新中,欢迎 点赞、收藏、关注,不要错过每一次窗口技巧的进阶!🚀

http://www.dtcms.com/a/466181.html

相关文章:

  • 如何简单的并且又能大幅度降低任务队列的锁粒度、提高吞吐量?
  • GPTL(General Purpose Timing Library)使用教程
  • 网站制作先做数据库还是前台重庆新闻论坛新闻评论
  • 【终端使用MySQL】MySQL 数据库核心操作全解析:从入门到性能优化
  • Oracle 数据库在海光平台上运行表现如何?附兼容性验证、性能优化与迁移实践
  • 网站后台账号密码忘记了怎么办宿迁558网络专业做网站
  • go学习1---》结构体和一些其他的东西
  • 兰州网站设计公司厦门网站建设中心
  • git 暂存恢复和文件误删恢复
  • TLS1.3后量子混合密钥协商技术解析及演进展望
  • flutter google play 应用不支持 16 KB
  • 无人机多处理协同作业控制姿态原理与实现
  • flutter mapbox_maps_flutter 应用不支持 16 KB
  • 佛山网站建设的首选求网站
  • 从 0 到 1 精通 MongoDB:实战场景 + 底层原理全解析
  • 建设门户网站的基本意义有哪些wordpress配置页面
  • 技术速递|使用 GitHub Copilot Agent 模式现代化 Java 项目的分步指南
  • 从Apache Doris 学习 HyperLogLog
  • RWA赋能艺术金融:艺术品代币化可行性的探索与展望
  • 成都市网站建html网站首页
  • 网站建设有关书籍创立网站做电商
  • Ansible学习----了解ansible
  • 什么是输入寄存器 什么是输出寄存器 什么是写输入寄存器 什么是读保持寄存器
  • 合网站建设郑州做网站优化地址
  • 现代软件工程课程 个人博客作业
  • 大连网站设计收费标准做免费网站需要营业执照吗
  • 网站打不开 ...有哪些网站做的比较好看
  • 网站建设团队成员网站flash代码
  • 后台启动java jar包的方法
  • 蓝桥杯 取球博弈