滑动窗口大总结!!!妈妈以后再也不担心我不会做滑动窗口啦~

news2024/9/21 0:26:55

写在前面:全部题都源于力扣

  • 讲解
  • 题目一:最小覆盖子串
  • 题目二:字符串排列
  • 题目三:找所有字母异位词
  • 题目四:无重复字符的最长子串
  • 题目五:滑动窗口的最大值

讲解

滑动窗口算法技巧主要用来解决子数组问题,比如让你寻找符合某个条件的最长/最短子数组。
如果用暴力解的话,你需要嵌套 for 循环这样穷举所有子数组,时间复杂度是O(n2)

for(int i = 0; i < nums.size(); i ++){
	for(int j = i; j < nums.size(); j ++){
		//维护一个nums[i,j]的子数组
	}
}

滑动窗口其实也并不难就是维护一个窗口,不断滑动,不断更新答案,大致逻辑:

int left = 0, right = 0;

while (right < nums.size()) {
    // 增大窗口
    window.addLast(nums[right]);
    right++;
    
    while (window needs shrink) {
        // 缩小窗口
        window.removeFirst(nums[left]);
        left++;
    }
}

由于这套逻辑left和right都不会回退,所以滑动窗口的时间复杂度是O(n)

没了,讲解到此结束在这里插入图片描述
只学讲解是没有办法乱杀滴,接下来就靠着这个模板魔改解决hard题吧!

题目一:最小覆盖子串

力扣难度hard->传送门
在这里插入图片描述
滑动窗口的思路是这样的:

  1. 在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right) 称为一个「窗口」。

问:为什么设计为左闭右开区间?
答:因为这样初始化 left = right = 0 时区间 [0, 0) 中没有元素,但只要让 right 向右移动(扩大)一位,区间 [0, 1) 就包含一个元素 0 了。如果设置为两端都开的区间,那么让 right 向右移动一位后开区间 (0,1) 仍然没有元素;如果设置为两端都闭的区间,那么初始区间 [0, 0] 就包含了一个元素。这两种情况都会给边界处理带来不必要的麻烦。

  1. 先不断地增加 right 指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 t 中的所有字符)。
  2. 此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 t 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果
  3. 重复第 2 和第 3 步,直到 right 到达字符串 s 的尽头。

talk is cheap,show me the code!

首先,需要window和need两个哈希表,用来记录窗口中已有的字符和需要凑齐的字符

// 记录 window 中的字符出现次数
unordered_map<char, int> window;
// 记录所需的字符出现次数
unordered_map<char, int> need;
for(char c : t) need[c] ++;

现在开始套模板,只需要思考以下几个问题:

  1. 什么时候应该移动 right 扩大窗口?窗口加入字符时,应该更新哪些数据?
    答:只要窗口内没有满足t字符都有的话就应该继续扩大窗口,窗口加入字符时,需要更新窗口大小(window++),必备字符个数(window[c]++),已满条件的字符数(valid++)。
while(right < s.size()){
	char c = s[right];//加入滑动窗口的值
	right ++;//窗口变大
	//新加入的值是否需要,需要的话:
	if(need(c)){
		window[c]++;//已有的必备值加加
		if(window[c] == need[c]) valid++;//如果某个字符在此窗口已经满足条件,valid++
	}
}
  1. 什么时候窗口应该暂停扩大,开始移动 left 缩小窗口?从窗口移出字符时,应该更新哪些数据?
    答:当 valid 满足 need 时应该收缩窗口,应该在收紧窗口的时候更新最终数据,更新窗口大小,更新valid(移除元素了,这里只可能减),window[字符]数量,另外更新最小覆盖子串的起始位置。因为答案一定是在缩窗口的时候出现的,所以应该在这里更新len和start
            // 判断左侧窗口是否要收缩
            while (valid == need.size()) {
                // 在这里更新最小覆盖子串
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }
                // d 是将移出窗口的字符
                char d = s[left];
                // 缩小窗口
                left++;
                // 进行窗口内数据的一系列更新
                if (need.count(d)) {
                    if (window[d] == need[d])
                        valid--;
                    window[d]--;
                }
  1. 我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
    答:一定是在缩小的时候

整体代码:

class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char, int> need, window;
        for (char c : t) {
            need[c]++;
        }
        int left = 0, right = 0;
        // 记录window中的字符满足need条件的字符个数
        int valid = 0;
        // 记录最小覆盖子串的起始索引及长度
        int start = 0, len = INT_MAX;
        while (right < s.size()) {
            // c 是将移入窗口的字符
            char c = s[right];
            // 扩大窗口
            right++;
            // 进行窗口内数据的一系列更新
            if (need.count(c)) {
                window[c]++;
                if (window[c] == need[c])
                    valid++;
            }
           // 判断左侧窗口是否要收缩
           // 用while!!一种缩到不能再缩
            while (valid == need.size()) {
                // 在这里更新最小覆盖子串
                // 必须先将len记录下来再更新窗口大小
                // 只有这样才能记录每一次合法len,然后更新
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }
                // d 是将移出窗口的字符
                char d = s[left];
                // 缩小窗口
                left++;
                // 进行窗口内数据的一系列更新
                if (need.count(d)) {
                    if (window[d] == need[d])
                        valid--;
                    window[d]--;
                }
            }
        }
        // 返回最小覆盖子串
        // 等于INT_MAX的话返回的是""不是" "
        return len == INT_MAX ? "" : s.substr(start, len);
    }
};

题目二:字符串排列

力扣567
在这里插入图片描述
mid难度,s1是可以包含重复字符的哦

是明显的滑动窗口算法,相当给你一个 S 和一个 T,请问你 S 中是否存在一个子串,包含 T 中所有字符且不包含其他字符?

基本和题目一是一样的,只有几个地方需要注意:

  1. 本题移动 left 缩小窗口的时机是窗口大小大于 t.length() 时,因为排列嘛,显然长度应该是一样的。
  2. 当发现 valid == need.size() 时,就说明窗口中就是一个合法的排列,所以立即返回 true。至于如何处理窗口的扩大和缩小,和最小覆盖子串完全相同。

完整代码:

class Solution {
public:
    bool checkInclusion(string t, string s) {
        unordered_map<char, int> need, window;
        for (char c : t) need[c]++;

        int left = 0, right = 0, valid = 0;
        while (right < s.size()) {
            char c = s[right++];
            if (need.count(c)) {
                window[c]++;
                if (window[c] == need[c]) valid++;
            }

            while (right - left > t.size()) { // 严格大于,以便准确控制窗口大小
                char d = s[left++];
                if (need.count(d)) {
                    if (window[d] == need[d]) valid--;
                    window[d]--;
                }
            }
			// valid == need.size()!!!
            if (right - left == t.size() && valid == need.size()) // 确保窗口大小严格等于t的长度
                return true;
        }
        return false;
    }
};

题目三:找所有字母异位词

力扣438
在这里插入图片描述
异位词,就是排列啊,搞了个高端的说法,也糊弄不了我们这些绝顶聪明的娃在这里插入图片描述
直接上代码

class Solution {
public:
    vector<int> findAnagrams(string s, string t) {
        unordered_map<char, int> need, window;
        for (char c : t) {
            need[c]++;
        }

        int left = 0, right = 0;
        int valid = 0;
        // 记录结果
        vector<int> res;
        while (right < s.size()) {
            char c = s[right];
            right++;
            // 进行窗口内数据的一系列更新
            if (need.count(c)) {
                window[c]++;
                if (window[c] == need[c]) {
                    valid++;
                }
            }
            // 判断左侧窗口是否要收缩
            while (right - left > t.size()) {
                char d = s[left];
                left++;
                // 进行窗口内数据的一系列更新
                if (need.count(d)) {
                    if (window[d] == need[d]) {
                        valid--;
                    }
                    window[d]--;
                }
            }
            if(right - left == t.size() && valid == need.size()){
                res.push_back(left);
            }
        }
        return res;
    }
};

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

力扣3.
这题在双指针里面用双指针解决过了
如果用滑动窗口的话也很容易
窗口缩的条件就是window[c] > 1,说明有重复了
和双指针思路一模一样
双指针:维护[j,i]数组

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int s[N];
int main(){
    int n;
    int r = 0;
    cin >> n;
    for(int i = 0, j = 0; i < n; i ++){
        cin >> a[i];
        s[a[i]] ++;//记录个数
        while(s[a[i]] > 1){
            -- s[a[j ++]];
        }
        r = max(r, i - j + 1);
    }
    cout << r;
}

滑动窗口:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int left = 0;
        int right = 0;
        int r = 0;
        unordered_map<char, int> window;
        while(right < s.size()){
            char c = s[right];
            right++;
            window[c]++;
            while(window[c] > 1){
                char d = s[left];
                window[d]--;
                left++;
            }
            r = max(r, right - left);
        }
        return r;
    }
};

题目五:滑动窗口的最大值

经典滑动窗口,力扣239
又名单调队列的实现
在这里插入图片描述

和题目1~4不一样,这题滑动窗口大小固定,每一次的移动也固定,难点在于求最大值

这里实现队列,要有pop,push操作,因为题目的特殊性,再加个返回最大值的max操作
我们需要逐步实现这三个API

push:
push 方法依然在队尾添加元素,但是要把前面比自己小的元素都删掉
在这里插入图片描述

void push(int n) {
    // 将前面小于自己的元素都删除
    while (!maxq.empty() && maxq.back() < n) {
          maxq.pop_back();
    }
    maxq.push_back(n);
}

max:
如果每个元素被加入时都这样操作,最终单调队列中的元素大小就会保持一个单调递减的顺序,因此我们的 max 方法可以可以这样写:

int max() {
      // 队头的元素肯定是最大的
      return maxq.front();
    }

pop:
pop 方法在队头删除元素 n:

void pop(int n) {
     if (n == maxq.front()) {
         maxq.pop_front();
     }
}

所以综合代码:

class slidingQueue{
private:
    deque<int> maxq;
public:
    void push(int n){
        while(!maxq.empty() && n > maxq.back()) maxq.pop_back();
        maxq.push_back(n);
    }
    int max(){
        return maxq.front();
    }
    void pop(int n){
        if(!maxq.empty() && maxq.front() == n) maxq.pop_front();
    }
};
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        slidingQueue window;
        vector<int> res;
        for(int i = 0; i < nums.size(); i ++){
            if(i < k - 1){
                window.push(nums[i]);
            }
            else{
                window.push(nums[i]);
                res.push_back(window.max());
                window.pop(nums[i - k + 1]);
            }
        }
        return res;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1973471.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

B 端产品设计:导航系统构建指南

两年前写的一篇关于导航菜单的文章帮助许多学生进入 B 端设计领域。然而&#xff0c;两年过去了&#xff0c;行业在不断发展&#xff0c;文章中的许多观点并不适用于当前的 B 端设计环境。如今的 B 端设计越来越受到重视&#xff0c;所以最近打算深入挖掘之前不太过时的文章内容…

strimzi operator 部署kafka集群(可外部访问)

Strimzi介绍 官方文档:https://strimzi.io/docs/operators/0.42.0/overview#kafka-components_str Strimzi介绍 Strimzi 是一个用于 Apache Kafka 在 Kubernetes 上部署和管理的开源项目。它提供了一组 Kubernetes 自定义资源定义(Custom Resource Definitions,CRDs)、控制…

充电宝有必要买贵的吗?充电宝可以带上高铁吗?充电宝选购方法

市面上的充电宝可以说是非常的多&#xff0c;但是能选到一款适合自己的充电宝基本是不容易的&#xff0c;然而&#xff0c;当我们准备选购充电宝时&#xff0c;常常会面临诸多疑问。其中&#xff0c;“充电宝有必要买贵的吗”就是一个备受关注的问题。价格似乎成为了我们在众多…

[Git][认识Git]详细讲解

目录 1.什么是仓库&#xff1f;2.认识工作区、暂存区、版本库3.认识 .git1.index2.HEAD && master3.objects4.总结 1.什么是仓库&#xff1f; 仓库&#xff1a;进⾏版本控制的⼀个⽂件⽬录 2.认识工作区、暂存区、版本库 工作区&#xff1a;在电脑上写代码或⽂件的⽬录…

【C++】C++应用案例-通讯录管理系统

目录 一、整体介绍 1.1、需求和目标 1.2、整体功能描述 二、页面及功能描述 2.1 主菜单 2.2 添加联系人菜单 2.3 显示联系人菜单 2.4 修改联系人菜单 2.5 退出功能 三、流程设计 3.1 主流程 3.2 添加操作流程 3.3 显示联系人操作流程 3.4 修改联系人操作流程 四…

V.PS荷兰阿姆斯特丹VPS详细测评

V.PS怎么样&#xff1f; V.PS的荷兰VPS位于荷兰阿姆斯特丹数据中心&#xff0c;实际的网络从测评的数据来看&#xff1a;电信走的CN2 GIA/AS4809网络、联通走的是CUII/AS9929网络、移动走的是CUII/AS9929网络&#xff0c;也就是说三网都是走的运营商的轻负载线路。 默认的CPU型…

c/c++自增运算符

自增运算符在前&#xff1a;先自增再取值 自增运算符在后&#xff1a;先取值再自增 如图&#xff1a; lptmp等于tmp&#xff0c;但是t等于128&#xff0c;也就说&#xff0c;当位于后面时&#xff0c;先取值&#xff0c;再自增。

数论第四节:二元一次不定方程、勾股数

不定方程定义 解不确定的方程称为不定方程。一般化的定义为&#xff1a;不定方程是指未知数的个数多余方程的个数&#xff0c;或未知数受到某种限制&#xff08;如整数、正整数等&#xff09;的方程和方程组。 二元一次不定方程定义 形如axbyc的形式的方程。其中a,b不等于0&…

python print 函数参数:sep 自定义分隔符,end 自定义结尾符

1. 简述 print 函数可以将内容打印到标准输出&#xff0c;如果不指定 end 参数&#xff0c;默认在输出的内容之后加一个 “回车符\n”。 以下是 print 函数常用的参数用法&#xff1a; print(object, …, sepstr, endstr) object, …&#xff1a;要打印的内容&#xff0c;可以…

如何基于欧拉系统完成第三方软件仓库的安装

首先&#xff0c;我们需要写一个镜像脚本 rootlocalhost yum.repos.d]# vim docker-ce.repo内容如下 [docker-ce] namedocker baseurlhttps://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/rhel/9/x86_64/stable/ //我们使用的是清华的镜像源 gpgcheck0 tips:这里告诉大家一…

来点八股文(五) 分布式和一致性

Raft raft 会进入脑裂状态吗&#xff1f;描述下场景&#xff0c;怎么解决&#xff1f; 不会。raft通过选举安全性解决了这个问题&#xff1a; 一个任期内&#xff0c;follower 只会投票一次票&#xff0c;且先来先得&#xff1b;Candidate 存储的日志至少要和 follower 一样新…

Kafka基本讲解

Kafka基本讲解 一&#xff1a;Kafka介绍 Kafka是分布式消息队列&#xff0c;主要设计用于高吞吐量的数据处理和消息传输&#xff0c;适用于日志处理、实时数据管道等场景。Kafka作为实时数仓架构的核心组件&#xff0c;用于收集、缓存和分发实时数据流&#xff0c;支持复杂的…

【单例设计模式】揭秘单例模式:从原理到实战的全方位解析(开发者必读)

文章目录 深入理解单例设计模式&#xff1a;原理、实现与最佳实践引言第一部分&#xff1a;设计模式简介第二部分&#xff1a;单例模式定义第三部分&#xff1a;单例模式的优点和缺点第四部分&#xff1a;单例模式的实现方式懒汉式非线程安全的实现线程安全的实现&#xff08;双…

vmware ubuntu虚拟机网络联网配置

介绍vmware虚拟机配置基础网络环境&#xff0c;同时连接外网&#xff08;通过桥接模式&#xff09;&#xff0c;以及ubuntu下输入法等基础工具安装。 本文基于ubuntu22.04&#xff0c;前提虚拟机已经完成安装。本文更多是针对vmware虚拟机的设置&#xff0c;之前有一篇针对ubun…

第三关:Git 基础知识

一、Git是什么 Git是一种开源的分布式版本控制系统&#xff0c;广泛应用于软件开发领域&#xff0c;尤其是在协同工作环境中。它为程序员提供了一套必备的工具&#xff0c;使得团队成员能够有效地管理和跟踪代码的历史变更。下面是 Git 的主要功能和作用的规范描述&#xff1a…

Java面试题——第二篇(设计模式)

1. 工厂方法模式 1.1 普通工厂模式 建立一个工厂类&#xff0c;对实现了同一接口的一些类进行实例的创建。 1.2 抽象工厂模式 抽象多个工厂类&#xff0c;提高工厂的可扩展性 定义抽象工厂接口 public interface DeviceFactory { Phone createPhone(); Computer creat…

【工具插件类教学】vHierarchy 2工具编辑器扩展使用

目录 一、下载导入 二、使用介绍 1.便捷小工具 a.图标和颜色Icons and colors b.对象组件缩略图Component minimap c.层级线展示Hierarchy lines d.极简模式Minimal mode e.斑马条纹图案Zebra striping f.激活切换Activation toggle 2、快捷键 一、下载导入 资源官方…

Redis系列之Redis Sentinel

概述 Redis主从集群&#xff0c;一主多从模式&#xff0c;包括一个Master节点和多个Slave节点。Master负责数据的读写&#xff0c;Slave节点负责数据的查询。Master上收到的数据变更&#xff0c;会同步到Slave节点上实现数据的同步。通过这种架构实现可以Redis的读写分离&…

U盘文件或目录损坏无法读取?专业恢复策略全解析

U盘困境&#xff1a;文件目录的隐形危机 在日常的数字生活中&#xff0c;U盘作为便捷的数据存储与传输工具&#xff0c;扮演着至关重要的角色。然而&#xff0c;当U盘中的文件或目录突然遭遇损坏&#xff0c;导致无法被正常读取时&#xff0c;这无疑给用户带来了极大的困扰。这…

达梦数据库的系统视图v$cachers

达梦数据库的系统视图v$cachers 达梦数据库的系统视图V$CACHERS的作用是显示缓存中的项信息&#xff0c;在 ini 参数 USE_PLN_POOL !0 时才统计。这个视图帮助数据库管理员监控和分析缓存的使用情况&#xff0c;优化数据库性能。通过查询V$CACHERS视图&#xff0c;可以获取缓存…