C++不知算法系列之滑动指针

公众号:编程驿站

1. 前言

双指针搜索算法,常见的有左右双指针;快慢双指针;先后双指针以及多指针……其中还包括一类滑动指针。滑动指针也称为滑动窗口指针,其搜索实现即有灵性又透着优雅。

本文通过几个案例聊聊滑动指针。

2. 最长的 1

题目描述

给定一个由若干 01 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。返回包含最长(连续)1子数组的长度。
A = [1,1,1,0,0,0,1,1,1,1,0], K = 2,则连续出现1的最长长度为6
把最后一个0和倒数第二个0变成1,即[1,1,1,0,0,1,1,1,1,1,1]可得到最长连续1的长度。

算法实现流程:

  • 初始,left指针(左指针)指向最左边,right指针(右指针)和left指针同位置。相当于初始窗口紧闭状态。

在这里插入图片描述

  • right指针不停地向右边移动不间断地扩大窗口。移动条件是right指针指向的值为1,如果right指针所指向位置的值为0,则查询是否还有把0转换成1的机会,有则通过把0转换成1后移动。否则,right指针停止移动。

  • 如下图所示,因k=2,可以将连续的两个0转换为1(红色表示0已经转换为1)。k的值在整个遍历过程中需要保留,可以另创建计数器变量c,计数已经使用过的0转换为1的次数。

  • 在这里插入图片描述

  • right指针停止移动后,意味着在原数列中找到了一段连续1的子序列。用变量res记录子序列的长度。

    Tips: 实际操作时,不需要真正把0变成1

在这里插入图片描述

  • 此时,right指针继续向前移动没有多大意义。需要等待左边释放用过的01的机会,方可以再次试探后面是否还有更多的连续1的子序列。

    left指针向右边移动,直到遇到图中标记为红色的0(表示此处用过一次0转换为1的机会),让计数器c自减一,释放一次机会。则为右指针争取到了一次新的机会,把right位置的0变换为1,且继续向右边移动,直到再次遇到0

    Tips:left指针释放机会后,意味着原来的0变回了0,left指针需要多向前移一位。

在这里插入图片描述

根据上述描述,总结一下:

  • 如果right指针所指向的值为1,则right指针向右边移动。
  • 如果right指针指向的值为0,可以试着用掉01的机会。如果没有了机会,统计此时rightleft之间1的个数。且right停止移动。
  • 移动left指针直到遇到0,释放用过的机会。且left停止移动。
  • 重复上述过程,直到right指针到达数组的末尾位置。

leftright移动时,类似一扇窗窗户在从左向右滑动,把这种双指针称为滑动指针。

滑动指针的特点:

  • 右指针移动过程中会扩大窗口范围,且移动过程会消费资源寻找解,如本题的资源就是消耗0变成1的机会。
  • 右指针在移动过程一旦消耗掉所有资源,就可以从左右指针所在范围内中获得一个解,此解不一定是最终解。
  • 通过移动左指针释放资源,如回收0变成1的机会。
  • 一旦释放资源后又继续让右指针移动,重复直到找到最终答案。

编码实现:

#include <bits/stdc++.h>
using namespace std;
int ones(int nums[],int len, int k) {int res=0,c=0;int left=0,right=0;while(right<len && left<len) {//right指针所指向的值为 1 或者还有机会while(nums[right]==1 || c<k  ) {//如果遇到0,则用掉机会if(nums[right]==0)c++;right++;}//当 right 停下来后,统计resres=max(res,right-left);//移动 left 释放机会while(  nums[left]!=0  ) left++;//释放机会c--;// 释放机会,意味着 1 又转换为 0,需要让 left 前进一位left++;}return res;
}
int main() {int nums[]= {1,0,1,0,0,0,1,1,0,1,0};int len=sizeof(nums)/4;int k=2;int res= ones(nums,len,k);cout<<res;return 0;
}

3. 查找最短子串

问题描述:

现有字符串 S和字符串 T,请在字符串 S 里面找出包含T所有字母的最小子串。如果 ·S· 中不存这样的子串,则返回空字符串,如果 S 中存在这样的子串,需要保证有唯一的答案。

如输入 S="ADOBECODEBANC",T="ABC"
则输出:"BANC"

算法分析:

此题中,可以把T当成资源,一边扫描S一边查找是否存在T中的字符,有则消耗掉,直到消耗掉T中的所有资源。得到一个解后再移动左指针,释放资源。重复这个过程。

下面使用图示演示整个过程。

  • 初始状态。左指针和右指针指向同一个位置。

在这里插入图片描述

  • 右指针向右边移动,直到包含完字符串T中的所有字符。

在这里插入图片描述

  • 移动左指针释放窗口中的资源字符前,计算此时左右指针之间的距离,即一个有效解,但不是最终解。直到左右窗口中没有完整的T字符串时激活右指针移动。

在这里插入图片描述

  • 左指针向右边移动,一边收窄窗口释放资源,一边统计此时左右之间的距离。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

总结一下:

  • 右指针向右移动直到左右指针所在窗口中包含完整的T字符串资源。
  • 左指针每移动一步后,只要左右窗口中的包含完整的T资源就计算当前的左右指针的距离,直到左右指针中不再包含完整的T资源。
  • 当右指针指向数列最末尾时,整个算法结束。

算法实现:算法实现过程中,需要如下几个信息。

  • 算法中需要记录实际所需要的资源信息。
  • 在扩展窗口时,统计被包含的资源信息以及记录是否已经完全包含。
#include <bits/stdc++.h>
using namespace std;string getShort(string str,string t) {int size=str.size();//左右指针初始位置int left=0,right=0;//记录资源信息int sources[26]= {0};//记录窗口中已经使用的资源int wins[26]= {0};//计数器int count=0;//实际有效资源的数量int scount=0;//记录最终位置和结束位置int sta=0,end=size;//把字符串数字化,便于比较for(int i=0; i<t.size(); i++) {//简化问题,假设字符串全部为大写sources[ t[i]-'A' ]++;scount++;}while( right<size ) {char c=str[right];//检查是不是资源if( sources[c-'A']!=0 ) {//记录在窗口中wins[c-'A']++;//如果某个字符全部在窗口中if(wins[c-'A']==sources[c-'A'])count++;}right++;while( count==scount  ) {//如果窗口中已经包含所有资源,则移动左窗口,移动之前计算此时左右指针的距离if(right-left<end-sta)sta=left,end=right;//移动左指针之前记录移出的字符char mc=str[left];//移动左指针left++;//检查移出去的字符是不是资源if( sources[mc-'A']!=0 ) {//从窗口信息表中移走wins[mc-'A']--;//某个字符全部释放if( wins[mc-'A']==0)count--;}}}return end-sta==size?"-1":str.substr(sta,end-sta);
}
int main() {string res= getShort("AAOBC","ABC");cout<<res;return 0;
}

4. 异位词

问题描述: 给定一个字符串s和一个非空字符串p,找到s中所有是p的字母异位词的子串,返回这些子串的起始索引。字符串只包含小写英文字母,并且字符串sp的长度都不超过20100

说明:字母异位词指字母相同,但排列不同的字符串。不考虑答案输出的顺序。
示例 1:
输入:s:"cbaebabacd" p:"abc"
输出:[0,6]
解释:起始索引等于0的子串是"cba"它是"abc"的字母异位词。起始索引等于6的子串是"bac",它是"abc”的字母异位词。

此题和上题的题意差不多。区别在于,左右指针所限制的窗口中除了要包含资源之外,还需要有长度限制,就是在窗口只能包含资源中的所有字符。

算法实现:

#include <bits/stdc++.h>
using namespace std;vector<int> getShort(string str,string t) {int size=str.size();//左右指针初始位置int left=0,right=0;//记录资源信息int sources[26]= {0};//记录窗口中已经使用的资源int wins[26]= {0};//计数器int count=0;//实际有效资源的数量int scount=0;//记录位置vector<int> res;//把字符串数字化,便于比较for(int i=0; i<t.size(); i++) {//简化问题,假设字符串全部为大写sources[ t[i]-'a' ]++;scount++;}while( right<size ) {char c=str[right];//检查是不是资源if( sources[c-'a']!=0 ) {//记录在窗口中wins[c-'a']++;//如果某个字符全部在窗口中if(wins[c-'a']==sources[c-'a'])count++;}right++;while( count==scount  ) {//如果窗口中已经包含所有资源,则移动左窗口,移动之前计算此时左右指针的距离if(right-left==t.size())//如果左右指针所包含的窗口的长度恰好等于资源字符串的长度,则存储存其位置res.push_back(left);//移动左指针之前记录移出的字符char mc=str[left];//移动左指针left++;//检查移出去的字符是不是资源if( sources[mc-'a']!=0 ) {//从窗口信息表中移走wins[mc-'a']--;if( wins[mc-'a']==0)count--;}}}return res;
}
//测试
int main() {vector<int> res= getShort("cbaebabacd","abc");for(int i=0;i<res.size();i++)cout<<res[i]<<"\t";return 0;
}

5. 总结

滑动指针也是双指针的一个特殊实现。右指针一路开疆拓土,当边疆到达指定的区域的大小后,左指针缩小窗口,尽可能找到最好的区域。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java EE】初识Spring Web MVC

文章目录 &#x1f334;什么是Spring Web MVC&#xff1f;&#x1f338;什么是Servlet呢? &#x1f332;MVC 定义&#x1f338;再理解Spring MVC &#x1f333;如何学习Spring MVC呢&#xff1f;⭕总结 &#x1f334;什么是Spring Web MVC&#xff1f; Spring Web MVC 是基于…

Github 2024-04-05Java开源项目日报Top9

根据Github Trendings的统计,今日(2024-04-05统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目9TypeScript项目1OpenAPI 生成器:基于规范自动生成API工具 创建周期:2155 天开发语言:Java协议类型:Apache License 2.0Star数量:1…

二叉树oj题(1)

1.检查两棵树是否相同 解题思路&#xff1a; 1.两棵树都为空&#xff0c;return true 2.一个为空&#xff0c;一个不为空&#xff0c;return false 3.节点不为空&#xff0c;但值不相同&#xff0c;return false 以上都是特殊情况&#xff0c;如果没有这些情况&#xff0c;就…

NoSQL概述

NoSQL概述 目录 一、为什么用NoSQL 二、什么是NoSQL 三、经典应用分析 四、N o S Q L 数 据 模 型 简 介 五、NoSQL四大分类 六、CAP BASE 一、为什么用NoSQL 1、单机MySQL的美好年代 在90年代&#xff0c;一个网站的访问量一般不大&#xff0c;用单个数据库完全可以轻松应…

理解Go语言中的并发和并行

即使有多年的并发编程经验,有些开发人员也可能无法清楚地理解并发(concurrency)和并行(parallelism)之间的区别。下面我们以一个真实的例子来说明:一家咖啡店。 在这家咖啡店中,一名服务员负责接收订单并使用一台咖啡机进行准备。顾客下订单,然后等待他们的咖啡。 …

99%的人不知道,Oracle resetlogs强制开库需要推进SCN?

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

蓝桥杯 十一届C++A组 字符排序 21分(运行超时)

思路&#xff1a; 1. 此题考查的冒泡排序中的交换次数&#xff0c;其实就是考察当前数与后面的逆序对个数问题。而为了最大利用位数&#xff0c;应当使每一位都不小于后面的字符&#xff0c;否则会造成一次逆序对的浪费&#xff08;贪心&#xff0c;为了使总位数最少&#xff…

贪心算法|55.跳跃游戏

力扣题目链接 class Solution { public:bool canJump(vector<int>& nums) {int cover 0;if (nums.size() 1) return true; // 只有一个元素&#xff0c;就是能达到for (int i 0; i < cover; i) { // 注意这里是小于等于covercover max(i nums[i], cover);if…

了解IP地址的基本概念和修改步骤

在数字化时代&#xff0c;IP地址作为网络设备的唯一标识&#xff0c;其重要性不言而喻。无论是为了提升网络性能&#xff0c;还是出于隐私保护的需求&#xff0c;修改IP地址都是网络使用者可能遇到的操作。虎观代理将详细介绍如何修改IP地址&#xff0c;并探讨在修改过程中需要…

Codeforces Round 832 (Div. 2) D. Yet Another Problem

题目 思路&#xff1a; #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e6 5, inf 1e18, maxm 4e4 5; c…

python_3

文章目录 题目运行结果模式A模式B模式C模式D 题目 mode input("请选择模式:") n int(input("请输入数字:"))if mode "A" or mode "a":# 模式A n:输入的层数 i:当前的层数# 每行数字循环次数 ifor i in range(1, n 1):for j in r…

C++语言学习(二)——⭐缺省参数、函数重载、引用

1.⭐缺省参数 &#xff08;1&#xff09;缺省参数概念 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时&#xff0c;如果没有指定实参则采用该形参的缺省值&#xff0c;否则使用指定的实参。 void Func(int a 0) {cout<<a<<endl; } int…