【算法】运用滑动窗口方法解决算法题(C++)

文章目录

  • 1. 滑动窗口 介绍
  • 2. 滑动窗口算法引入
    • 209.长度最小的子数组
  • 3. 使用滑动窗口解决算法题
    • 3.无重复字符的最长子串
    • 1004.最大连续1的个数III
    • 1658.将x减到0的最小操作数
    • 904.水果成篮
    • LCR015.找到字符串中所有字母异位词
    • 30.串联所有单词的子串
    • 76.最小覆盖子串

1. 滑动窗口 介绍

滑动窗口算法 通常用于解决字符串和数组相关的问题(如找子串、子数组)

该算法的基本思想是维护一个固定大小的窗口,通过该窗口在字符串或数组上滑动,从而寻找符合条件的子串或子数组。在每次窗口滑动时,我们只需要对窗口内的元素进行简单的更新,以便快速得到新的结果。


2. 滑动窗口算法引入

209.长度最小的子数组

在这里插入图片描述

解法思路

  • 解法一:暴力枚举
    • 两层循环,记录所有子数组的大小,最后得出满足条件的最小值
    • 时间开销大,时间复杂度O(n^2)
for (int i = 0; i < n; i++)
        int sum = 0;
        for (int j = i; j < n; j++)
            sum += nums[j];
            if (sum >= target)
            // ...
  • 解法二:根据①单调性 使用 ②同向双指针

在这里插入图片描述

根据上图所示,而单调性+同向双指针的方法,我们称之为滑动窗口
而滑动窗口算法解题一般分为以下步骤:

  1. 初始化指针left,right
  2. 控制元素入窗口(即开始移动右指针)
  3. 条件判断(根据题目要求)
  4. 出窗口(移动左指针)
  • 需要注意的是,后三步的执行顺序和题目有关,并非固定的

代码

int minSubArrayLen(int target, vector<int>& nums) {
    // 滑动窗口
    int n = nums.size();
    int left = 0, right = 0;
    int sum = 0, len = INT_MAX;
    
    while(right < n)
    {
        sum += nums[right];   // 从前向后遍历数组 计算sum
        
        while(sum >= target) // 计算结果,更新sum
        {
            len = min(len, right - left + 1);
            sum -= nums[left++];
        }
    
        ++right;
    }
    
    // 如果没有满足条件的len,则返回0
    return len == INT_MAX ? 0 : len;
}

3. 使用滑动窗口解决算法题

3.无重复字符的最长子串

解法思路

  • 解法一:暴力枚举+哈希表

    • 通过两层循环记录子串,并用哈希表记录所出现的字符,如果字符出现重复,则break此次循环
    • 时间复杂度O(n^2)
  • 解法二:找规律,使用滑动窗口+哈希表
    在这里插入图片描述

代码

int lengthOfLongestSubstring(string s) {
    int hash[128] = {0}; // 用数组模拟哈希表
    int ret = 0;
    for(int left = 0, right = 0; right < s.size(); ++right)
    {
        hash[s[right]]++; // 记录当前字符并++right

        while(hash[s[right]] > 1) // 遇到重复数,Left到重复数(前面的)的后一位

        {
            hash[s[left++]]--; 
        }

        ret = max(ret, right - left + 1);
    }

    return ret;
}

1004.最大连续1的个数III

在这里插入图片描述

解法思路

  • 根据题目,我们可以将k个0翻转成1,由此我们可以将题目要求转化为:

    • 找到最长的子数组,其中0的个数 <= k,由此只需要根据条件找最长即可
  • 解法一:暴力枚举+zero计数器

    • zero计数器用于统计子数组中0的个数
    • 通过两层循环计算子数组
    • 时间复杂度O(n^2)
  • 解法二:滑动窗口+zero计数器

    1. 初始化left、right指针
    2. 进窗口:每次循环末尾将right++
    3. 判断条件:根据当前值更新计数器
    4. 出窗口:当zerok,此时移动left指针,直到zero <= k
    5. 更新结果:每次循环尾部更新ret

代码

int longestOnes(vector<int>& nums, int k) {
    int left = 0, right = 0;
    int zero = 0, ret = 0; // 计数器,统计0的个数

    while(right < nums.size())
    {
        // 判断
        if(nums[right] == 0)    zero++;
        
        // 此时已经达到了最大能翻转的0的个数
        while(zero > k){
            if(nums[left] == 0)   zero--;
            left++;
        }
        
        // 更新结果
        ret = max(ret, right - left + 1);
        right++; // 移动窗口
    }

    return ret;
}

1658.将x减到0的最小操作数

在这里插入图片描述

解法思路

  • 如果我们直接进行解题,对于这个数组,我们可以从左右两个方向移除元素,比较复杂,所以我们可以采用下面的方法
  • 正难则反将题目要求、思路逆转过来!
    • 题目要求 找到将x减到0 最小操作数
    • 我们只需找到数组中值为 n(数组长度) - x 的 最长子数组 即可
    • 由此我们可以想到使用滑动窗口解题

在这里插入图片描述

  • 解法:滑动窗口
    1. 初始化指针和相关变量
    2. 进窗口:记录子串当前位置总和(tmp += nums[right])
    3. 条件判断:当前子串和>target
      • 出窗口:tmp -= nums[left],移动left指针
    4. 更新结果:当当前子串值tmp == target,更新结果ret
    5. 最后返回n - ret

代码

int minOperations(vector<int>& nums, int x) {
    // 思路:正难则反(将该题反过来思考)
    // 求和为target(sum-x)的最长子数组
    int sum = 0, n = nums.size();
    for(int num : nums) sum += num; // 计算数组总和 
    int target = sum - x, ret = -1;

    // x可能大于sum,此时return -1
    if(target < 0) return -1;

    // 解法:滑动窗口
    for(int left = 0, right = 0, tmp = 0; right < n; ++right)
    {
        tmp += nums[right]; // 进窗口

        while(tmp > target) // 判断
            tmp -= nums[left++]; // 出窗口
        if(tmp == target)
            ret = max(ret, right - left + 1); // 更新结果
    }

    // 由于我们是求的值为sum-x的最长子数组,按照题目要求返回n-ret
    return ret == -1 ? ret : n - ret;
}

904.水果成篮

在这里插入图片描述

解法思路

  • 题意分析:即找到最长子数组,子数组要求只有两种元素

  • 解法一:暴力枚举+哈希表

    • 暴力解法前面都有提到,类似的思路,这里跳过
  • 解法二:滑动窗口+哈希表

    1. 进窗口:将right位置元素统计到哈希表中
    2. 条件判断:当kinds(元素种类)>2
      • 出窗口:将hash中left位置元素减去,移动left指针
    3. 更新结果:每次在循环尾部更新结果ret

代码

int totalFruit(vector<int>& fruits) {
    // 该题实际上可以理解为:求最长子数组,要求子数组只有两种类型的水果
    int left = 0, right = 0, ret = 0, n = fruits.size();
    // unordered_map<int, int> hash; // 哈希表统计某种类水果出现次数
    int hash[100001] = {0}; // 小优化,由于水果种类<=10^5,使用数组作为哈希表
    int kinds = 0;
    
    // 分析题目,解法为滑动窗口
    while(right < n)
    {
        if(hash[fruits[right]] == 0)    kinds++; // 使用变量判断水果种类
        hash[fruits[right]]++; // 进窗口
        
        while(kinds > 2) // 判断
        {
            hash[fruits[left]]--; // 出窗口
            if(hash[fruits[left]] == 0)
                kinds--;
            ++left;
        }
        
        ret = max(ret, right - left + 1); // 更新结果
        ++right;
    }
    
    return ret;
}

LCR015.找到字符串中所有字母异位词

在这里插入图片描述

解法思路

  • 题意分析:题目要求找到所有的字母异位词,即找满足条件的子数组
  • 解法:滑动窗口+哈希表
    1. 先用两个哈希表将s、p中字符的出现次数分别统计
      • hash1 存储p中各个元素的出现次数
      • hash2 记录当前滑动窗口内各个元素的出现次数
    2. 进窗口:将right位置元素统计到hash2中,并记录当前有效字符的个数count
    3. 条件判断:当滑动窗口(即当前统计的子串)过大,
      • 出窗口:移动left指针,并判断有效字符
    4. 更新结果:循环尾部,每次判断(有效字符个数==p长度),则将该索引位置(left)加入到ret中

代码

vector<int> findAnagrams(string s, string p) {
    int hash1[26] = {0}; // hash1 存储p中各个元素的出现次数
    for(char ch : p)    hash1[ch - 'a']++;
    int count = 0, pLen = p.size(); // count 记录有效字符的个数
    int hash2[26] = {0}; // 记录每次滑动窗口元素出现次数
    vector<int> ret;
    
    for(int left = 0, right = 0; right < s.size(); ++right)
    {
        char in = s[right];
        hash2[in - 'a']++; // 进窗口
        if(hash2[in - 'a'] <= hash1[in - 'a'])  count++; // 维护count,满足有效字符条件则++
        if(right - left + 1 > pLen) // 滑动窗口过大
        {
            char out = s[left++];
            if(hash2[out - 'a'] <= hash1[out - 'a']) count--;
            hash2[out - 'a']--;
        }

        if(count == pLen)   ret.push_back(left); // 更新结果
    }
    return ret;
}

30.串联所有单词的子串

在这里插入图片描述

解法思路

在这里插入图片描述

  • 题意分析:我们需要找到所有串联子串在s中的开始索引
  • 解法:滑动窗口+哈希表
    • 这道题与上一个题字母异位词 类似
    1. 如图所示,滑动窗口需执行len次,先嵌套一层for循环
    2. 内层循环执行滑动窗口操作↓:
      • hash1用于统计 s 中当前窗口内各个字符串出现的次数
      • hash2统计words中每个字符串的出现次数
    3. 进窗口:记录当前right位置的字符串in,如果满足条件则入hash1并更新count(有效字符串个数)
    4. 条件判断:如果窗口的大小是否超过了包含所有 words 字符串的最大可能长度
      • 出窗口:将hash中left位置字符串删去,移动left指针
    5. 更新结果:如果有效字符个数count == words大小,则将索引left加入到结果ret

代码

关于代码注释中提到的小优化:

在这里插入图片描述

// Similar to LCR 015. 找到字符串中所有字母异位词
vector<int> findSubstring(string s, vector<string>& words) {
    int len = words[0].size(), wSize = words.size();
    vector<int> ret; // 结果数组
    if(len > s.size())  return ret;

    unordered_map<string, int> hash2; // 统计 words 字符串次数
    for(string str : words)   hash2[str]++; // 统计次数

    for(int i = 0; i < len; ++i) // 执行len次
    {
        unordered_map<string, int> hash1; // 存储 s 中各字符串的次数
        for(int left = i, right = i, count = 0; right + len <= s.size(); right += len) // right每次跳过一个words中字符串大小
    // count 计算有效字符串的个数
        {
            string in = s.substr(right, len); // right位置字符串
            if(hash2.count(in) && ++hash1[in] <= hash2[in]) count++; // 进窗口 (hash2.count(in),小优化如果hash2中没有in,就不执行,方括号会创建变量)
            // 判断
            if(right - left + 1 > len * wSize) // 出窗口 + 维护count
            {
                string out = s.substr(left, len);
                if(hash2.count(out) && hash1[out] <= hash2[out]) count--;
                hash1[out]--;
                left += len;
            }

            if(count == wSize) ret.push_back(left);
        }
    }   
    return ret;
}

76.最小覆盖子串

在这里插入图片描述

解法思路

  • 该题与上一题串联思路相似,重点在于条件判断
  • 题意分析:题目要求返回字符串s的最小子串,该子串包含字符串t的所有字符
  • 解法:滑动窗口+哈希表
    • 先初始化相关变量和哈希表
      • hash1统计t所有字符
      • hash2记录s中当前窗口字符的出现次数
    1. 进窗口:若right位置字符满足条件,则放入hash2中,并更新count(有效字符的个数)
    2. 条件判断:根据下图,当count == kinds时:
      在这里插入图片描述
    • 更新结果:如果此时滑动窗口大小 < minLen(当前记录的最短子串长),则更新结果
    • 出窗口:将left位置字符从hash2中减去,判断是否是满足条件的字符,如果是则count–

代码

string minWindow(string s, string t) {
    int hash1[128] = {0}; // 统计t的字符
    int hash2[128] = {0}; // 统计字符串
    int kinds = 0; // 统计t中 字符的种类
    for(char ch : t) 
        if(hash1[ch]++ == 0) kinds++;

    int minLen = INT_MAX, begin = -1; // 最后要用的结果,最小子串的长度和起始位置
    for(int left = 0, right = 0, count = 0; right < s.size(); ++right)
    {
        char in = s[right];// 进窗口
        if(++hash2[in] == hash1[in]) count++;    // 当t中的字符次数与当前in的次数一致时,更新count

        // 判断
        while(count == kinds)
        {
            if(right - left + 1 < minLen)
            {
                minLen = right - left + 1; // 更新结果
                begin = left; // 记录待返回字符串的起始索引
            }

            char out = s[left++]; // 出窗口
            if(hash2[out]-- == hash1[out]) count--;
        }
    }
    return begin == -1 ? "" : s.substr(begin, minLen);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

6个火爆全网的AI开源项目,用上月10万+

标题月10万可能说的有点夸张和含糊&#xff0c;10万具体指的是你可以利用这些开源项目实现&#xff1a; 访问量10万 收入10万 用户10万 …… 开源项目只是免费的工具&#xff0c;具体怎么实现还需要你根据自己需求去深入运营。这里只是给你推荐一些比较热门的开源项目&…

搞定Apache Superset

踩雷了无数次终于解决了Superset的一系列问题 现在是北京时间2023年12月27日&#xff0c;亲测有效。 Superset概述 Apache Superset是一个现代的数据探索和可视化平台。它功能强大且十分易用&#xff0c;可对接各种数据源&#xff0c;包括很多现代的大数据分析引擎&#xff…

MyBatis多表映射

1. 多表映射概念 MyBatis 思想是&#xff1a;数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式&#xff0c;可惜它们并不都是那样。 如果能有一种数据库映射模式&#xff0c;完美适配所有的应用程序查询需求&#xff0c;那就太…

二叉树详解(深度优先遍历、前序,中序,后序、广度优先遍历、二叉树所有节点的个数、叶节点的个数)

目录 一、树概念及结构(了解) 1.1树的概念 1.2树的表示 二、二叉树概念及结构 2.1概念 2.2现实中的二叉树&#xff1a; 2.3数据结构中的二叉树&#xff1a; 2.4特殊的二叉树&#xff1a; 2.5 二叉树的存储结构 2.51 顺序存储&#xff1a; 2.5.2 链式存储&…

【快速全面掌握 WAMPServer】09.如何在 WAMPServer 中安装 Composer

网管小贾 / sysadm.cc WAMPServer 的大名想必应该有不少人特别是新手小白们略有耳闻吧。 它是出自法国大神之手的一款 PHP 开发环境集成包&#xff0c;工作于 Windows 环境&#xff0c;类似于它这样的集成包在 Linux 平台上反正我是没找到&#xff0c;所以它应该算是对使用 Wi…

sparkstreamnig实时处理入门

1.2 SparkStreaming实时处理入门 1.2.1 工程创建 导入maven依赖 <dependency><groupId>org.apache.spark</groupId><artifactId>spark-streaming_2.12</artifactId><version>3.1.2</version> </dependency> <dependency…

【MySQL表的增删查改】

文章目录 前言1 Create1.1 单行数据 全列插入1.2 多行数据 指定列插入1.3 插入否则更新1.4 替换 2 Retrieve2.1 SELECT 列2.1.1 全列查询2.1.2 指定列查询2.1.3 查询字段为表达式2.1.4 为查询结果指定别名2.1.5 结果去重 2.2 WHERE 条件2.2.1 英语不及格的同学及英语成绩 ( &…

CocoaPods安装及‘__rvm_make -j8‘处理

CocoaPods是一个用Ruby写的、负责管理iOS项目中第三方开源库的工具&#xff0c;CocoaPods能让我们集中的、统一管理第三方开源库&#xff0c;为我们节省设置和更新第三方开源库的时间。 安装步骤 1.查看ruby版本 ruby -v 2.通过rvm来安装或升级Ruby&#xff0c;依次执行 cu…

Apache OFBiz RCE漏洞复现(CVE-2023-51467)

0x01 产品简介 Apache OFBiz是一个电子商务平台,用于构建大中型企业级、跨平台、跨数据库、跨应用服务器的多层、分布式电子商务类应用系统。 0x02 漏洞概述 漏洞成因 该系统的身份验证机制存在缺陷,可能允许未授权用户通过绕过标准登录流程来获取后台访问权限。此外,在…

【PTA-C语言】实验七-函数与指针I

如果代码存在问题&#xff0c;麻烦大家指正 ~ ~有帮助麻烦点个赞 ~ ~ 目录——实验七-函数与指针I 6-1 弹球距离&#xff08;分数 10&#xff09;6-2 使用函数输出一个整数的逆序数&#xff08;分数 10&#xff09;6-3 使用函数求最大公约数&#xff08;分数 10&#xff09;6-4…

使用Pycharm给html文件添加浏览器

1、选择菜单栏的File---->选择setting设置 2、选择Tools(工具)---> Web Browser(web 浏览器) 勾选 自己想要添加的浏览器前面 的勾选框即可 注意点击ok进行保存

《数据结构、算法与应用C++语言描述》- 平衡搜索树 -全网唯一完整详细实现插入和删除操作的模板类

平衡搜索树 完整可编译运行代码见&#xff1a;Github::Data-Structures-Algorithms-and-Applications/_34Balanced search tree 概述 本章会讲AVL、红-黑树、分裂树、B-树。 平衡搜索树的应用&#xff1f; AVL 和红-黑树和分裂树适合内部存储的应用。 B-树适合外部存储的…

github使用技巧(经验篇)

相关经验 指定代码范围并高亮显示 例如&#xff0c;指定nn_ops.py文件2612-L2686行的代码&#xff1a;https://github.com/tensorflow/tensorflow/blob/v2.14.0/tensorflow/python/ops/nn_ops.py#L2612-L2686 FAQ Q&#xff1a;github网页打不开&#xff1f; 【github加载不…

Java项目调试实战:如何高效调试Spring Boot项目中的GET请求,并通过equalsIgnoreCase()解决大小写不一致问题

Java项目调试实战&#xff1a;如何高效调试Spring Boot项目中的GET请求&#xff0c;并通过equalsIgnoreCase解决大小写不一致问题 写在最前面全部过程Java equalsIgnoreCase() 方法idea中如何调试SpringBoot项目在IntelliJ IDEA中使用内置HTTP客户端设置断点和调试 补充&#x…

PiflowX组件-WriteToUpsertKafka

WriteToUpsertKafka组件 组件说明 以upsert方式往Kafka topic中写数据。 计算引擎 flink 有界性 Streaming Upsert Mode 组件分组 kafka 端口 Inport&#xff1a;默认端口 outport&#xff1a;默认端口 组件属性 名称展示名称默认值允许值是否必填描述例子kafka_h…

Unity坦克大战开发全流程——结束场景——通关界面

结束场景——通关界面 就照着这样来拼 写代码 hideme不要忘了 修改上一节课中的代码

旅游网站Xtrip 前端模板html推荐

一、需求分析 旅游网站的功能可以根据具体的业务需求和目标进行不同的设计和实现&#xff0c;但是以下是一些常见的旅游网站功能&#xff0c;供参考&#xff1a; 酒店预订功能&#xff1a;用户可以搜索并预订酒店&#xff0c;查看酒店的详细信息、价格、评价和照片&#xff0c…

MySQL 8.0 InnoDB Tablespaces之General Tablespaces(通用表空间/一般表空间)

文章目录 MySQL 8.0 InnoDB Tablespaces之General Tablespaces&#xff08;通用表空间/一般表空间&#xff09;General tablespaces&#xff08;通用表空间/一般表空间&#xff09;通用表空间的功能通用表空间的限制 创建通用表空间&#xff08;一般表空间&#xff09;创建语法…

Linux磁盘与文件管理

目录 一、磁盘介绍 1. 磁盘数据结构 2. 磁盘的接口类型 3. 磁盘在Linux上的表现形式 二、磁盘分区与MBR 1. 分区优缺点 2. 分区方式 3. MBR分区 4. GPT分区 三、文件系统 1. 文件系统的组成 2. 默认的文件系统 3. 文件系统的作用 4. 模拟破坏文件与修复文件 4…

项目总结报告

《项目总结报告》 1.项目概要&#xff08;项目基本信息&#xff0c;项目期间&#xff0c;项目成果&#xff0c;项目开发工具环境&#xff09; 2.项目工作分析&#xff08;需求变更&#xff0c;计划与进度实施&#xff0c;投入情况&#xff0c;收益情况&#xff0c;质量情况&…