【数据结构】滑动窗口算法详解:高效解决子串问题

devtools/2024/10/24 2:33:54/

滑动窗口(Sliding Window)是一种常用于处理数组或字符串中子序列问题的算法技巧。它通过维护一个窗口来限制待处理的数据范围,从而高效地解决问题,避免重复计算。它的时间复杂度通常为 O(N),相较于暴力破解(时间复杂度 O(N^2))大大提升了效率。

如果你还不了解什么是子串或者时间复杂度,可参考以下文章:

  • 【计算机科学】什么是子串?全面解析及应用场景
  • 数据结构】时间复杂度和空间复杂度是什么?

滑动窗口的核心思想

滑动窗口可以看作是在数组或字符串上移动的一个固定大小的子区间。通过将窗口在数据上滑动,我们能够逐步考察不同的子区间,进而找到满足条件的解。滑动窗口的两个关键操作是:

  1. 扩展窗口(Expand Window):增大窗口的右边界,以包括新的元素。
  2. 收缩窗口(Shrink Window):缩小窗口的左边界,以排除不满足条件的元素。

在滑动窗口问题中,通常需要找到满足某些条件的子数组或子字符串的最优解(如最大值、最小值、特定元素组合等)。因此,滑动窗口常用于以下几类问题:

  • 最小或最大子数组和
  • 找到包含特定字符的最小子串
  • 连续子序列的最优解

算法步骤

  1. 使用两个指针 leftright 表示窗口的左右边界。
  2. 使用一个变量来记录窗口中的字符是否重复。
  3. 外层循环扩展 right 指针,将字符加入窗口。如果满足条件,将其加入窗口;否则,通过移动 left 指针移除最左边界。
  4. 统计并记录窗口的最大长度。

算法模板

// l 控制左边界,r 控制右边界,每次循环右边界不断拓展
for (int l = 0, r = 0; r < s.length(); ++r) {// 判断是否需要收缩左边界(check()函数的具体实现)while (l <= r && check()) {// 实现左边界的收缩}// 进行统计操作
}

算法实战:无重复字符的最长子串

题目链接:无重复字符的最长子串

题目描述:给定一个字符串 s,找到其中不包含重复字符的最长子串的长度。

示例 1:

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

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。

算法构建

  1. 使用 left, right 两个指针维护左右边界。
  2. 使用一个哈希集合来维护字符是否出现过。
  3. 外层循环不断扩展右边界,内层循环判断当前字符是否已经出现过。如果已出现,则不断收缩左边界直到满足条件。
  4. 将当前字符标记为已出现。
  5. 计算最长子串(通过右边界和左边界的差值)。

代码实现:

using namespace std;int lengthOfLongestSubstring(const string& s) {int max_length = 0; // 最长子串长度unordered_set<char> char_set; // 用于记录出现的字符int left = 0; // 左边界// 外循环控制右边界for (int right = 0; right < s.length(); ++right) {// 收缩左边界,直到当前字符不再出现while (char_set.find(s[right]) != char_set.end()) {char_set.erase(s[left++]);}char_set.insert(s[right]); // 插入当前字符max_length = max(max_length, right - left + 1); // 更新最大长度}cout << "最长无重复子串的长度为: " << max_length << endl;return max_length;
}
为何 r - l + 1 要额外 + 1

r - l + 1 是为了计算当前不重复子串的长度。

  • rl 分别是右边界和左边界,表示当前窗口的范围。
  • 当前窗口的长度是从索引 lr,但如果直接计算 r - l,结果只会包含从 lr-1 的元素,因为这个差值不包括右边界 r 自身。
  • 因此,我们需要加上 1 来确保当前窗口的长度包含 r 对应的字符。

例如,假设 l = 2r = 4,当前窗口是从索引 2 到 4,那么子串的实际长度应该是 4 - 2 + 1 = 3,表示字符在索引 2、3、4 这三个位置。

原理分析

我们可以使用测试用例1来举例:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以此不断类推,可得到最终的 ans 答案为 3。

复杂度分析

由于只需要对 n 访问一次,所以时间复杂度为 O(N)。

在最坏的情况下,char_set 需要存储 n 个字符,因此空间复杂度为 O(N)。

类比暴力枚举

相比常见的暴力枚举,滑动窗口方法高效得多。暴力枚举的例子如下:

class Solution {
public:int lengthOfLongestSubstring(const string& s) {int max_len = 0; // 使用更具语义化的变量名for (int i = 0; i < s.length(); ++i) {int t = getSubstringLength(s, i);max_len = std::max(max_len, t); // 使用 std::max}cout << max_len << endl;return max_len;}private:static int getSubstringLength(const string& s, int index) {int t = 0;unordered_set<char> seen; // 使用 unordered_set 简化for (int i = index; i < s.length(); ++i) {char c = s[i];if (seen.find(c) == seen.end()) {t++;seen.insert(c);} else {return t;}}return t;}
};

暴力枚举的时间复杂度为 O(N^2),因为外层循环每执行一次,内层循环都会从 index 循环到 n(输入字符串的长度)。总的执行次数为:

n + (n - 1) + (n - 2) + … + 1,其为等差数列,和为 n(n + 1) / 2,因此时间复杂度为 O(N^2)。

在空间复杂度方面,seen 在最坏情况下可能存储 n 个字符,因此复杂度为 O(N)。

总结

滑动窗口算法通过优化重复计算,显著提高了处理子串问题的效率。理解其原理和应用场景,可以帮助我们在算法竞赛及实际编程中更有效地解决问题。


http://www.ppmy.cn/devtools/128338.html

相关文章

QGraphics类型学习使用【Qt】【C++】

QGraphics类型学习使用 需求过程全部完整代码 首先已知&#xff0c;QGraphicsView&#xff0c;QGraphicsScene, QGraphicsItem&#xff0c;分别称为&#xff1a;视图&#xff0c;场景&#xff0c;图元&#xff0c;图表就是各种各样的元素&#xff0c;图片元素&#xff0c;线条元…

HCIP-HarmonyOS Application Developer 习题(十)

1、HarmonyOS设备A上的应用通过调用分布式任务调度的能力continuesbility&#xff0c;向设备B的应用发起跨端迁移&#xff0c;此过程属于跨端迁移中的哪个流程? A、流转准备 B、流转进行 C、流转结束 D、流转完成 答案&#xff1a;D 分析&#xff1a; 2、为了帮助用户通过全局…

Apache 出现 “403 forbidden“ 排查方法

1、检查运行 Apache 进程的用户没有对目录具备读取权限 如果该用户没有对 Directory 指定的目录具备适当的读取权限&#xff0c;就会导致 403 错误。 ​​例如&#xff1a;使用用户apache启动Apache进程&#xff0c;但是apache用户对 Directory 指定的目录没有读取权限 2、检查…

微前端 Spa qiankun

简介 首先什么是微前端&#xff1f; 他是一个软件架构模式。借鉴了后端的为服务架构思想&#xff0c;是将复杂单一的前端进行拆分成多个可以独立开发、部署、维护的小型应用。不同的应用关注不同的业务。最终将其集成到一个主框架里面。简单来说就是先分后合。 传统前端开发的…

其他-自己手动更换汽车电磁进排气阀0.9.2

其他-自己手动更换汽车电磁进排气阀0.9.0 背景本次工具流程注意参考 2024年10月18日08:57:00—0.9.2 背景 昨天手动更换了电磁阀&#xff0c;记录下过程和注意事项&#xff0c;简单总结了一下 本次工具 10号套筒和工具老虎钳锤子一字改刀新的进排气电磁阀 流程 打开引擎盖…

计算机网络——传输层服务

传输层会给段加上目标ip和目标端口号 应用层去识别报文的开始和结束

R数据科学 16.5.3练习题

(1) 编写代码以使用一种映射函数完成以下任务。 a. 计算 mtcars 数据集中每列的均值。 b. 确定 nycflights13::flights 数据集中每列的类型。 c. 计算 iris 数据集中每列唯一值的数量。 d. 分别使用 μ -10、0、10 和 100 的正态分布生成 10 个随机数。 library(purrr) # 计算…

Cadence元件A属性和B属性相互覆盖

最近在使用第三方插件集成到Cadence,协助导出BOM到平台上&#xff0c;方便对BOM进行管理和修改&#xff0c;结果因为属性A和属性B不相同&#xff0c;导致导出的BOM错误。如下图&#xff1a; ​​ 本来我们需要导出Q12&#xff0c;结果给我们导出了Q13&#xff0c;或者反之&…