当前位置: 首页 > news >正文

算法<C++>——双指针 | 滑动窗口

一、背景

定长滑窗套路:
窗口右端点在 i 时,由于窗口长度为 k,所以窗口左端点为 i−k+1。

我总结成三步:入-更新-出。

入:下标为 i 的元素进入窗口,更新相关统计量。如果窗口左端点 i−k+1<0,则尚未形成第一个窗口,重复第一步。
更新:更新答案。一般是更新最大值/最小值。 出:下标为 i−k+1 的元素离开窗口,更新相关统计量,为下一个循环做准备。
以上三步适用于所有定长滑窗题目。**

如果用暴力解的话,你需要嵌套 for 循环这样穷举所有子数组,时间复杂度是 O(N2)

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

滑动窗口算法技巧的思路也不难,就是维护一个窗口,不断滑动,然后更新答案,该算法的大致逻辑如下:

// 索引区间 [left, right) 是窗口
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)阶。

// 滑动窗口算法伪码框架
void slidingWindow(string s) {// 用合适的数据结构记录窗口中的数据,根据具体场景变通// 比如说,我想记录窗口中元素出现的次数,就用 map// 如果我想记录窗口中的元素和,就可以只用一个 intauto window = ...int left = 0, right = 0;while (right < s.size()) {// c 是将移入窗口的字符char c = s[right];window.add(c);// 增大窗口right++;// 进行窗口内数据的一系列更新...// *** debug 输出的位置 ***printf("window: [%d, %d)\n", left, right);// 注意在最终的解法代码中不要 print// 因为 IO 操作很耗时,可能导致超时// 判断左侧窗口是否要收缩while (window needs shrink) {// d 是将移出窗口的字符char d = s[left];window.remove(d);// 缩小窗口left++;// 进行窗口内数据的一系列更新...}}
}

二、实操题目

76. 最小覆盖子串 | 力扣 | LeetCode | (hard)

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:


> 输入:s = "ADOBECODEBANC", t = "ABC"
> 输出:"BANC" 解释:最小覆盖子串 "BANC" 包含来自字符串 t
>'A''B''C'

示例 2:

> 输入:s = "a", t = "a" 
> 输出:"a" 解释:整个字符串 s 是最小覆盖子串。

示例 3:

> 输入: s = "a", t = "aa" 
> 输出: "" 解释: t 中两个字符 'a' 均应包含在 s 的子串中,
> 因此没有符合条件的子字符串,返回空字符串。

提示:

m == s.length n == t.length 1 <= m, n <= 105 s 和 t 由英文字母组成 进阶:你能设计一个在
o(m+n) 时间内解决此问题的算法吗? 题目来源:力扣 76. 最小覆盖子串。

第一次尝试因为没弄清楚涵盖的意思,导致运行错误,初步学习后:

class Solution {
public:bool is_cover(int n1[],int n2[]){for(int i='A';i<='Z';i++){if(n1[i]<n2[i])return false;}for(int i='a';i<='z';i++){if(n1[i]<n2[i])return false;}return true;}string minWindow(string s, string t) {int n1[200]{};int n2[200]{};int n=s.length(),m=t.length();for(int i=0;i<m;i++){n2[t[i]]++;}int res_l=-1,res_r=n,l=0;for(int r=0;r<n;r++){n1[s[r]]++;while(is_cover(n1,n2)){if(r-l<res_r-res_l){res_l=l;res_r=r;}n1[s[l]]--;l++;}}return res_l<0?"":s.substr(res_l,res_r-res_l+1);}
};

灵神优化代码:

用一个变量 less 维护目前子串中有 less 种字母的出现次数小于 t 中字母的出现次数。

具体来说(注意下面算法中的 less 变量):

  1. 初始化 ansLeft=−1, ansRight=m,用来记录最短子串的左右端点,其中 m 是 s 的长度。
  2. 用一个哈希表(或者数组)cntT 统计 t 中每个字母的出现次数。
  3. 初始化 left=0,以及一个空哈希表(或者数组)cntS,用来统计 s子串中每个字母的出现次数。
  4. 初始化 less 为 t 中的不同字母个数。
  5. 遍历 s,设当前枚举的子串右端点为 right,把字母c=s[right] 的出现次数加一。加一后,如果 cntS[c]=cntT[c],说明 c 的出现次数满足要求,把 less 减一。
  6. 如果less=0,说明 cntS 中的每个字母及其出现次数都大于等于 cntT 中的字母出现次数,那么:
  • 如果 right−left<ansRight−ansLeft,说明我们找到了更短的子串,更新 ansLeft=left, ansRight=right。
  • 把字母 x=s[left] 的出现次数减一。减一前,如果 cntS[x]=cntT[x],说明 x 的出现次数不满足要求,把 less 加一。
  • 左端点右移,即 left 加一。
  • 重复上述三步,直到 less>0,即 cntS有字母的出现次数小于 cntT 中该字母的出现次数为止。
  1. 最后,如果ansLeft<0,说明没有找到符合要求的子串,返回空字符串,否则返回下标 ansLeft 到下标 ansRight 之间的子串。
class Solution {
public:string minWindow(string s, string t) {int cnt[128]{};int less = 0;for (char c : t) {if (cnt[c] == 0) {less++; // 有 less 种字母的出现次数 < t 中的字母出现次数}cnt[c]++;}int m = s.size();int ans_left = -1, ans_right = m;int left = 0;for (int right = 0; right < m; right++) { // 移动子串右端点char c = s[right]; // 右端点字母cnt[c]--; // 右端点字母移入子串if (cnt[c] == 0) {// 原来窗口内 c 的出现次数比 t 的少,现在一样多less--;}while (less == 0) { // 涵盖:所有字母的出现次数都是 >=if (right - left < ans_right - ans_left) { // 找到更短的子串ans_left = left; // 记录此时的左右端点ans_right = right;}char x = s[left]; // 左端点字母if (cnt[x] == 0) {// x 移出窗口之前,检查出现次数,// 如果窗口内 x 的出现次数和 t 一样,// 那么 x 移出窗口后,窗口内 x 的出现次数比 t 的少less++;}cnt[x]++; // 左端点字母移出子串left++;}}return ans_left < 0 ? "" : s.substr(ans_left, ans_right - ans_left + 1);}
};作者:灵茶山艾府

567. 字符串的排列 | 力扣 | LeetCode | (medium)

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。
换句话说,s1 的排列之一是 s2 的 子串 。

示例 1:

输入:s1 = "ab" s2 = "eidbaooo" 
输出:true 解释:s2 包含 s1 的排列之一 ("ba"). 

示例 2:

输入:s1= "ab" s2 = "eidboaoo" 
输出:false

提示:
1 <= s1.length, s2.length <= 104 s1 和 s2 仅包含小写字母 题目来源:力扣 567. 字符串的排列。

class Solution {
public:bool checkInclusion(string s1, string s2) {int n = s1.length();if (n > s2.length())return false;array<int, 26> cnt_s1;for (char c : s1) {cnt_s1[c - 'a']++;}array<int, 26> cnt_s2;for (int i = 0; i < s2.length(); i++) {// incnt_s2[s2[i] - 'a']++;if (i + 1 < n)continue;//updateif (cnt_s1 == cnt_s2)return true;//outcnt_s2[s2[i - n + 1] - 'a']--;}return false;}
};

灵神优化

把每次循环的 cntS1 == cntT 从 O(∣Σ∣) 优化成 O(1)。

class Solution {
public:bool checkInclusion(string s1, string s2) {int m = s1.size();if (m > s2.size()) {return false;}int cnt[26]{};int less = 0;for (char c : s1) {if (cnt[c - 'a'] == 0) {less++;}cnt[c - 'a']++;}for (int i = 0; i < s2.size(); i++) {// 1. 进入窗口int c = s2[i] - 'a';cnt[c]--;if (cnt[c] == 0) {less--;}if (i < m - 1) { // 窗口大小不足 mcontinue;}// 2. 判断子串 t 的每种字母的出现次数是否均与 s1 的相同if (less == 0) {return true;}// 3. 离开窗口,为下一个循环做准备int out = s2[i - m + 1] - 'a';if (cnt[out] == 0) {less++;}cnt[out]++;}return false;}
};作者:灵茶山艾府

1456. 定长子串中元音的最大数目 | 力扣 | LeetCode | (medium)

给你字符串 s 和整数 k 。

请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。

英文中的 元音字母 为(a, e, i, o, u)。

示例1:

输入:s = "abciiidef", k = 3
输出:3
解释:子字符串 "iii" 包含 3 个元音字母。

示例2:

输入:s = "aeiou", k = 2
输出:2
解释:任意长度为 2 的子字符串都包含 2 个元音字母。

用滑动窗口尝试一下

class Solution {
public:int maxVowels(string s, int k) {int n=s.length(),count=0,ans=INT_MIN;for(int i=0;i<n;i++){if(s[i]=='a'||s[i]=='o'||s[i]=='e'||s[i]=='u'||s[i]=='i')count++;//inint left=i-k+1;if(left<0) continue;ans=max(ans,count);//updatechar out=s[left];if(out=='a'||out=='o'||out=='i'||out=='e'||out=='u')count--;//out}return ans;}
};

438. 找到字符串中所有字母异位词 | 力扣 | LeetCode | (medium)

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

示例1:

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词

示例2:

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

代码尝试:

class Solution {
public:vector<int> findAnagrams(string s, string p) {vector<int> res;int n=s.length(),m=p.length();array<int,26> n1;array<int,26> n2;for(int i=0;i<m;i++){n1[p[i]-'a']++;}for(int i=0;i<n;i++){n2[s[i]-'a']++;int left=i-m+1;if(left<0) continue;if(n1==n2){res.push_back(left); }n2[s[left]-'a']--;}return res;}
};

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

给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度

示例1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca""cab" 也是正确答案。

示例2:

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

之前没接触哈希表,第一次想到用数组尝试:


class Solution {
public:int lengthOfLongestSubstring(string s) {int n=s.length(),left=0,res=0;array<int,26> m;for(int i=0;i<n;i++){m[s[i]-'a']++;while((m[s[i]-'a'])>1){m[s[left]-'a']--;left++;}res=max(res,i-left+1); }return res;}
};

总结到,上面的代码仅限小写字母场景。

使用哈希表后:

class Solution {
public:int lengthOfLongestSubstring(string s) {int n = s.length(), res = 0, left = 0;unordered_map<char, int> cnt;for (int right = 0; right < n; right++) {cnt[s[right]]++;while (cnt[s[right]] > 1) {cnt[s[left]]--;left++;}res = max(res, right - left + 1);}return res;}
};
http://www.dtcms.com/a/556722.html

相关文章:

  • HarmonyOS数据存储Kit深度实践:从架构设计到性能优化
  • 【JUnit实战3_21】第十二章:JUnit 5 与主流 IDE 的集成 + 第十三章:用 JUnit 5 做持续集成(上):在本地安装 Jenkins
  • Java算法题分享(一)
  • 简单、高效且低成本的预训练、微调与服务,惠及大众基于 Ray 架构设计的覆盖大语言模型(LLM)完整生命周期的解决方案byzer-llm
  • 软件测试-BUG篇
  • 写入瓶颈到削峰填谷:基于 Redis 与 MySQL 的高并发写入工程化方案
  • 一些常见的编程软件
  • Nginx Stream模块开发:TCP/UDP代理扩展
  • 开网站做外贸中国做网站正邦
  • K8s Dashboard运维技巧全面经验总结
  • 合肥创业网从百万到千万 网站怎么优化
  • Flutter boost权威指南
  • 人工智能、机器学习与神经网络:解锁智能时代的核心密码
  • Kubernetes 上的 GitLab + ArgoCD 实践(三):使用 ArgoCD 打通 CD 流程
  • spark-SQL学习
  • SSM基于网络安全维护的机房设备管理19rya(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • ProcessLifecycleOwner 完全指南:优雅监听应用前后台状态
  • html css js网页制作成品——珠帘玉幕HTML+CSS网页设计(4页)附源码
  • 开启RN之旅——小试牛刀
  • Unity使用PP-MattingV2实现人像分割
  • 智能模型对齐(一致性)alignment
  • VSCode SSH远程连接失败 最速解决方案
  • 网站开发旅游前台模板临海建设规划局网站
  • 加载YOLO模型,处理mp4视频
  • 基于 GEE 利用 GHSL(100m)数据的区域建成区时空变化量化分析
  • day22_用户授权 头像上传
  • 网站识别爬虫(包括以浏览器插件形式运行的爬虫)主要通过分析请求特征、行为模式等差异来区分人类用户和自动化程序
  • 网站建设费用IPseo官网优化详细方法
  • 汽车OTA CDN HTTPS MQTT OCSP
  • python异步编程 -- 深入理解事件循环event-loop