算法魅力-双指针之滑动窗口的叛逆

#1024程序员节#征文

216fe205150546fda849a555c57559a6.png

目录

1.滑动窗口的定义

2.算法实战

2.1 长度最小的子数组

算法思路

2.2 无重复字符的最长子串

算法思路

2.3 最大连续 1 的个数 III

算法思路

哈希表的简要补充

结束语

祝大家1024程序节快乐!!! 


1.滑动窗口的定义

 滑动窗口是一种双指针算法的特例,主要用于处理连续区间的问题,特别是在字符串或数组上寻找满足某些条件的连续子区间。在滑动窗口中,通常有个指针,分别称为“窗口的起始指针”和“窗口的结束指针”,它们一起构成一个“窗口”,在数组或字符串上移动。

滑动窗口的几个关键特点
1.动态调整大小:窗口的大小和位置会根据问题的需求动态地调整。
2.连续性:窗口内的元素是连续的。
3.方向性:窗口通常从数组的左端开始,向右移动。

滑动窗口算法的步骤
- 初始化两个指针,通常称为`left`和`right`,它们分别表示窗口的起始和结束位置。
- 移动`right`指针来扩展窗口,直到窗口中的元素满足某个条件。
- 当窗口满足条件后,尝试通过移动`left`指针来缩小窗口,同时保持窗口中的元素满足条件。
- 在移动指针的过程中,更新所需的答案(例如最大或最小长度)。

简而言之,可以理解成两个指针的同向移动。

滑动窗口算法常用于以下典型问题:
- 最长不含重复字符的子字符串:在字符串中找到一个最长子串,该子串不包含任何重复字符。
- 最小覆盖子串:在字符串S中找到一个包含字符串T中所有字符的最短连续子串。

2.算法实战

2.1 长度最小的子数组

209. 长度最小的子数组 - 力扣(LeetCode)

e9abf9ac38124e4981f05462f84aa430.png

算法思路

1.暴力枚举(超时)

从前往后枚举数组中的任意一个元素,把它当成起始位置。然后从这个起始位置开始,然
后寻找一段最短的区间,使得这段区间的和「大于等于」目标值。
将所有元素作为起始位置所得的结果中,找到最短的数组即可。

class Solution {
public:
 int minSubArrayLen(int target, vector<int>& nums) {
 // 记录结果
 int ret = INT_MAX;
 int n = nums.size();
 
 // 枚举开始位置
 for (int start = 0; start < n; start++)
 {
 int sum = 0; // 记录从这个位置开始的连续数组的和
 // 寻找结束位置
 for (int end = start; end < n; end++)
 {
 sum += nums[end]; // 将当前位置加上
 
 if (sum >= target) // 当这段区间内的和满⾜条件时
 {
 // 更新结果,start 开头的最短区间已经找到
 ret = min(ret, end - start + 1);
 break;
 }
 }
 }
 // 返回最后结果
 return ret == INT_MAX ? 0 : ret;
 }
};

补充:将ret赋值无穷大,即=INT_MAX

2.滑动窗口解法

让滑动窗口满足:从 i 位置开始,窗口内所有元素的和小于 target (当窗口内元素之和
第一次大于等于目标值的时候,就是 i 位置开始,满足条件的最小长度)。
做法:将右端元素划入窗口中,统计出此时窗口内元素的和,如果窗口内元素之和大于等于 target, 更新结果,并且将左端元素划出去的同时继续判 断是否满足条件并更新结果(因为左端元素可能很小,划出去之后依旧满足条件)
如果窗口内元素之和不满足条件: right++ ,另下一个元素进入窗口。
那么为何滑动窗口可以解决问题,并且时间复杂度更低?
这个窗口寻找的是:以当前窗口最左侧元素(记为 left1 )为基准,符合条件的情况。也
就是在这道题中,从 left1 开始,满足区间和 sum >= target 时的最右侧(记为
right1 )能到哪里。
既然已经找到从 left1 开始的最优的区间,那么就可以舍去 left1 。但是如果继续像方法一一样,重新开始统计第二个元素( left2 )往后的和,势必会有大量重复的计算(因为在求第一段区间的时候,已经算出很多元素的和了,这些和是可以在计算下次区间和的时候用上的)。
此时, rigth1 的作用就体现出来了,我们只需将 left1 这个值从 sum 中剔除。从right1 这个元素开始,往后找满足 left2 元素的区间(此时 right1 也有可能是满足的,因为 left1 可能很小。 sum 剔除掉 left1 之后,依旧满足大于等于target )。这样我们就能省掉大量重复的计算。
时间复杂度:虽然代码是两层循环,但是我们的 left 指针和 right 指针都是不回退的,两者
最多都往后移动 n 次。因此时间复杂度是O(n).

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
      //sort(nums.begin(),nums.end());
        int len=INT_MAX,n=nums.size();
        int sum=0;
       // int left=0;
        for(int left=0, right=0;right<n;right++){
           
           sum += nums[right];
           while(sum>=target){
             len=min(len,right-left+1);
             sum-=nums[left++];
           }    
        }
        return len==INT_MAX?0:len;
    }
};

2.2 无重复字符的最长子串

3. 无重复字符的最长子串 - 力扣(LeetCode)

2cde9d8b105e48308ba4a01c2c3896bb.png

算法思路

1.暴力枚举

首先在暴力枚举的过程中,可以通过哈希表来记录字符出现的次数,简单来说,就是定义一个数组,每个字符对应的ASCII值是数组的相应位置。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n=s.size(),ret=0,i,j;
        for( i=0;i<n;i++){
            int hash[128]={0};
            for( j=i;j<n;j++){
                hash[s[j]]++;
               if(hash[s[j]]>1)
                break;
            }
            ret=max(ret,j-i);
        }
        return ret;
    }    
};

当字符字数大于一的时候,就跳出内循环,更新结果,更改数组首元素。

2.滑动窗口

在暴力枚举的过程中,我们发现当进行新的一层外循环时,只要还在遇到重复字母的范围内,就会重复遍历相同的子串子集,并且长度是减小的。

所以我们可以省略这些操作,让滑动窗口满足:窗口内所有元素都是不重复的。

做法:右端元素 ch 进入窗口的时候,哈希表统计这个字符的频次:
如果这个字符出现的频次超过 1 ,说明窗口内有重复元素,那么就从左侧开始划出窗口, 直到 ch 这个元素的频次变为 1 ,然后再更新结果。
如果没有超过 1 ,说明当前窗口没有重复元素,可以直接更新结果
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n=s.size();
        int left=0,right=0,ret=0;
        int hash[128]={0};
        while(right<n){
            hash[s[right]]++;//进入窗口
            while(hash[s[right]]>1){//判断
                hash[s[left]]--;
                left++;}//出窗口
                //ret=max(ret,(right-1)-(left-1)+1);
                ret=max(ret,right-left+1);//更新结果
            right++;//下一个元素进入窗口
        }
        return ret;
    }
};

2.3 最大连续 1 的个数 III

1004. 最大连续1的个数 III - 力扣(LeetCode)

0febcc46c0a44bc798b41c8eaa8c156b.png

算法思路

1.暴力枚举(超时)

用两个循环嵌套,固定首数组元素,然后遍历,枚举出所有情况。定义一个count来储存0的数量。

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
       int n=nums.size();
       int i,j,len=0;
       for(i=0;i<n;i++){
        int count=0;
        for(j=i;j<n;j++){
            if(nums[j]==0)
            count++;
            if(count>k){
               break;
            }
            len=max(len,j-i+1);
        }
       }
       return len;
    }
};

2.滑动窗口

可以把问题转化成:求数组中一段最长的连续区间,要求这段区间内 0 的个数不超过 k 个。 既然是连续区间,可以考虑使用「滑动窗口」来解决问题。
class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int len=0,zero=0;
        for(int left=0,right=0;right<nums.size();right++){
            if(nums[right]==0) zero++;
            while(zero>k){
                if(nums[left++]==0){
                    zero--;
                }
            }
            len=max(len,right-left+1);
        }
        return len;
    }
};

算法采用双指针技术来维护一个窗口,该窗口包含最多 k个0。通过调整窗口的左右边界,我们可以找到包含最多1的子数组。
算法步骤
1. 初始化变量:
   - len:记录当前找到的最长子数组的长度。
   - zero:记录当前窗口中0的数量。
   - left和 right:分别表示滑动窗口的左右边界。
2. 遍历数组:
   - 使用 right指针遍历数组 nums。
   - 如果 `nums[right]` 是0,则增加 zero计数。
3. 调整窗口大小:
   - 当 zero的数量超过 k 时,表示窗口中的0太多,需要通过增加 left 指针来缩小窗口,直到窗口中的0的数量不大于 k。
   - 在增加 left指针的过程中,如果 nums[left] 是0,则减少 zero计数。
4. **更新最长子数组长度**:
   - 在每次调整窗口后,计算当前窗口的长度(`right - left + 1`),并与 len比较,更新 len为最大值。
5. 返回结果:
   - 遍历完成后,返回 len 作为结果,它表示最长的连续1的子数组的长度,同时允许最多 k 个0。
 

哈希表的简要补充

哈希表(Hash table),也被称作散列表,是一种数据结构,用于存储键值对,并能够实现快速查找。哈希表通过一个哈希函数来计算每个键的哈希值,哈希值决定了在表中的位置。

以下是哈希表的一些关键特性:
1. 哈希函数:哈希表使用哈希函数将键映射到表中一个位置来存储对应的值。理想的哈希函数应该容易计算,并且能够将不同的键均匀分布到哈希表中。
2. 冲突解决:由于哈希函数可能会将不同的键映射到同一个位置,这种情况称为“冲突”。解决冲突的方法有多种,比如链地址法(在同一位置存储多个元素,形成一个链表),开放寻址法(寻找下一个空位置)等。
3. 查找效率:在理想情况下,哈希表的查找、插入和删除操作的时间复杂度可以达到O(1)。然而,在最坏的情况下(例如,所有键都映射到同一个位置),这些操作的时间复杂度可能会退化到O(n)。
4. 动态扩容:当哈希表中的元素数量达到一定比例时,哈希表可能会进行扩容,以维持操作的效率。扩容通常涉及创建一个更大的表,并将所有现有元素重新哈希到新表中。
5. 负载因子:负载因子是哈希表中元素数量与哈希表大小的比例。通常,当负载因子超过某个阈值时,哈希表会进行扩容。

结束语

本篇博客就到此结束啦,后面还会更新该算法的相关题目,也会更多有趣的算法陆续产出,感谢友友们一路来的支持!!!

祝大家1024程序节快乐!!! 

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

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

相关文章

操作系统笔记(二)进程,系统调用,I/O设备

什么是进程? 一个正在执行的程序一个包含运行一个程序所需要的所有信息的容器进程的信息保存在一个进程表中( Process Table)。进程表中的每一项对应一个进程,称为进程控制块(Process control block,PCB)。 PCB信息包括: 用户ID(UID)、进程ID(PID)…

【开源免费】基于SpringBoot+Vue.JS在线视频教育平台(JAVA毕业设计)

本文项目编号 T 027 &#xff0c;文末自助获取源码 \color{red}{T027&#xff0c;文末自助获取源码} T027&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 新…

黑马JavaWeb-day03

文章目录 Ajax前后端分离开发前端工程化环境准备Vue项目Vue项目开发流程 Vue组件库ElementVue路由打包部署 Ajax Ajax:Asynchronous JavaScript And XML,异步的JavaScript和XML 作用: 数据交换:通过Ajax可以给服务器发送请求,并获取服务器相应的数据异步交互:可以在不重新加载…

1971. 寻找图中是否存在路径

有一个具有 n 个顶点的 双向 图&#xff0c;其中每个顶点标记从 0 到 n - 1&#xff08;包含 0 和 n - 1&#xff09;。图中的边用一个二维整数数组 edges 表示&#xff0c;其中 edges[i] [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接&#x…

ShardingSphere 分库分表入门实战

分库分表 需求分析 如果我们的平台发展迅速&#xff0c;用户量激增&#xff0c;从数据库层面去思考&#xff0c;哪个表的数据会最大呢&#xff1f; 回顾一下我们的数据库设计&#xff1a; 1&#xff09;app 应用表 显然不会&#xff0c;成百上千的应用已经多&#xff0c;但…

Chrome DevTools:Console Performance 汇总篇

Chrome DevTools Chrome 开发者工具是一套 Web 开发者工具&#xff0c;直接内置于 Google Chrome 浏览器中。 开发者工具可以帮助您即时修改页面和快速诊断问题&#xff0c;最终帮助您更快地构建更好的网站。 一、开启 DevTools 右上角菜单 > 更多工具 > 开发者工具 页面…

2015-2022年《中国县城建设统计年鉴》面板数据附下载链接

2015-2022年《中国县城建设统计年鉴》面板数据 数据简介 《中国县城建设统计年鉴》是由住建部编辑的&#xff0c;旨在全面反映我国县城建设与发展状况的统计资料。该年鉴根据各省、自治区和直辖市建设行政主管部门上报的历年县城建设统计数据编辑而成&#xff0c;每年公布一次…

Vue-插槽slot

当我们封装一个组件时&#xff0c;不希望里面的内容写死&#xff0c;希望使用的时候能够自定义里面的内容&#xff0c;这时我们就需要使用到插槽 插槽是什么呢 插槽是子组件提供给父组件的一个占位符&#xff0c;用slot标签表示&#xff0c;父组件可以在这个标签填写任何模板代…

Python自动化测试:解锁高效测试的十大魔法秘诀!

在Python自动化测试领域&#xff0c;最佳实践能够帮助提升测试效率、确保测试质量&#xff0c;并促进团队间的协作。以下是Python自动化测试的十大最佳实践&#xff0c;使用Markdown格式进行展示&#xff1a; 1. 明确测试目标和范围 描述&#xff1a;在开始编写自动化测试之前&…

MCK主机加固与防漏扫的深度解析

在当今这个信息化飞速发展的时代&#xff0c;网络安全成为了企业不可忽视的重要议题。漏洞扫描&#xff0c;简称漏扫&#xff0c;是一种旨在发现计算机系统、网络或应用程序中潜在安全漏洞的技术手段。通过自动化工具&#xff0c;漏扫能够识别出系统中存在的已知漏洞&#xff0…

全面击破工程级复杂缓存难题

目录 一、走进业务中的缓存 &#xff08;一&#xff09;本地缓存 &#xff08;二&#xff09;分布式缓存 二、缓存更新模式分析 &#xff08;一&#xff09;Cache Aside Pattern&#xff08;旁路缓存模式&#xff09; 读操作流程 写操作流程 流程问题思考 问题1&#…

openpnp - 在顶部相机/底部相机高级校正完成后,需要设置裁剪所有无效像素

文章目录 openpnp - 在顶部相机/底部相机高级校正完成后&#xff0c;需要设置裁剪所有无效像素概述笔记设置后的顶部相机效果设置后的底部相机效果 备注END openpnp - 在顶部相机/底部相机高级校正完成后&#xff0c;需要设置裁剪所有无效像素 概述 用自己编译的基于openpnp-…

《PP-OCRv1》论文精读:PaddleOCR是目前SOTA级别的OCR开源技术(截止2024年10月)

PP-OCR: A Practical Ultra Lightweight OCR System论文地址PP-OCRv2: Bag of Tricks for Ultra Lightweight OCR System论文地址PP-OCRv3: More Attempts for the Improvement of Ultra Lightweight OCR System论文地址PaddleOCR Github OCR工具库 43.5K个star PP-OCRv1由百度…

探索Python与Excel的无缝对接:xlwings库的神秘面纱

文章目录 探索Python与Excel的无缝对接&#xff1a;xlwings库的神秘面纱1. 背景介绍&#xff1a;为何选择xlwings&#xff1f;2. xlwings是什么&#xff1f;3. 如何安装xlwings&#xff1f;4. 简单的库函数使用方法打开工作簿创建工作簿读取单元格数据写入单元格数据保存并关闭…

Flink on yarn模式下,JobManager异常退出问题

这个问题排除了很久&#xff0c;其中更换了Flink版本&#xff0c;也更换了Hadoop版本一直无法解决&#xff0c;JobManager跑着跑着就异常退出了。资源管理器上是提示运行结束&#xff0c;运行状态是被Kill掉。 网上搜了一圈&#xff0c;都说内存不足、资源不足&#xff0c;配置…

支持国密算法的数字证书-国密SSL证书详解

在互联网中&#xff0c;数字证书作为标志通讯各方身份信息的数字认证而存在&#xff0c;常见的数字证书大都采用国际算法&#xff0c;比如RSA算法、ECC算法、SHA2算法等。随着我国加强网络安全技术自主可控的大趋势&#xff0c;也出现了支持国密算法的数字证书-国密SSL证书。那…

namenode格式化连接8485端口失败

报错如下 解决方式&#xff1a; 配置了 Hadoop HA&#xff0c;但没有启动JournalNode服务&#xff0c;启动命令如下&#xff1a; hadoop-daemon.sh start journalnode

蓝桥杯——搜索

搜索 DFS基础回溯 回溯法简介&#xff1a; 回溯法一般使用DFS&#xff08;深度优先搜索&#xff09;实现&#xff0c;DFS是一种遍历或搜索图、树或图像等数据结构的算法&#xff0c;当然这个图、树未必要存储下来&#xff08;隐式处理就是回溯法&#xff09;&#xff0c;常见…

075_基于springboot的万里学院摄影社团管理系统

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

jmeter中发送post请求遇到的问题

用jmeter发送post请求&#xff0c;把请求参数放在Body Data处&#xff0c;参数都写得正确&#xff0c;但没想到结果每次都报错&#xff0c;直接响应结果乱七八糟&#xff0c;改成用Parameters,反而不乱报错了。 上图 请求里如下 另外一些请求也是这样 这个响应结果也是错误的…