滑动窗口
思想:
滑动窗口算法的思想是通过维护一个窗口,在一个序列上滑动该窗口以寻找特定条件的子序列。
也有用虫取法更生动形象:滑动窗口的两个指针移动的过程和虫子爬动的过程非常像:后脚不动,把前脚向前移动;前脚不动,把后脚移动过来。
滑动窗口算法的基本思路如下:
- 定义两个指针left和right,分别指向窗口的左右边界。
- 移动右指针,扩大窗口,直到满足特定条件为止。
- 移动左指针,缩小窗口,直到不满足特定条件为止。
- 重复步骤2和步骤3,直到右指针到达序列的末尾。
滑动窗口算法的优势在于可以避免对每个子序列进行暴力枚举,从而提高时间效率。它的应用范围非常广泛,如字符串匹配、最小覆盖子串、最长无重复子串等问题都可以使用滑动窗口算法进行解决。
引入简单示例:
Leetcode 209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
完全符合上边的思路,按照思路写:java
思路:
public class 长度最小的子数组 {
public int minSubArrayLen(int target, int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}//nums为空或长度为0直接返回不存在
int left = 0, right = 0;//定义初始值从0开始
int sum = 0, minLen = 100001;//最小长度定义为大数
while (right < nums.length) {
//右边框从左往右增大
sum += nums[right];
right++;
while (sum >= target) {
//当边框中数的和大于等于target,记录此时最小长度,
// 并向右移动左边框减小范围
minLen = Math.min(minLen, right - left);
sum =sum-nums[left];
left++;
}
}
if (minLen==100001)//即所有数加起来也没有target大,返回0
return 0;
else return minLen;
}
}
字符串的排列
给你两个字符串 s1
和 s2
,写一个函数来判断 s2
是否包含 s1
的排列。如果是,返回 true
;否则,返回 false
。
换句话说,s1
的排列之一是 s2
的 子串 。
示例 1:
输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").
示例 2:
输入:s1= "ab" s2 = "eidboaoo"
输出:false
一看就是需要用滑动窗口的,用数组下标表示每个小写字母的ascii码,数组内存字母出现的次数。
public class 字符串的排列567 {
//用滑动窗口写
//突破点:s1,s2都为小写字母,可以用ascii码来表示,
//所以可以创建长度为26的数组来存储值
// 即97-‘a’到122- ‘a’就是从零开始的下标了
public boolean checkInclusion(String s1, String s2) {
if (s1.length() > s2.length()) return false;
int[] a = new int[26];
int[] b = new int[26];
for (int i = 0; i < s1.length(); i++) {
a[s1.charAt(i) - 'a']++;
}//把s1中的值先存起来
for (int i = 0; i < s1.length(); i++) {
//存储s2的前s1长度的值
b[s2.charAt(i) - 'a']++;
}
if(check(a, b)){//调方法判断是否相同,
// 因为在下边循环就直接进入新一轮了
return true;
}
for (int i=s1.length();i<s2.length();i++){
b[s2.charAt(i)-'a']++;
b[s2.charAt(i-s1.length())-'a']--;
if(check(a, b)){
return true;
}
}
return false;
}
public boolean check(int[] a,int [] b){
for (int i = 0; i <26 ; i++) {
if(a[i]!=b[i])
return false;
}
return true;
}
}
进阶题目:无重复字符的最长子串:3
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1: 输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
public class 无重复字符的最长子串3 {
//创建集合存放窗口,
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int maxLength = 0;
int left = 0;
int right = 0;
HashSet<Character> set = new HashSet<>();
// set用于存储窗口内的字符,方便直接对元素查找
while (right < n) {//遍历
if (!set.contains(s.charAt(right))) {
//判断窗口内是否含有字符s.charAt(right),如果不含有,则将该字符添加到set中,
// 更新maxLength为当前窗口的最大长度。
set.add(s.charAt(right));
maxLength = Math.max(maxLength, right - left + 1);
right++;
} else {//含有字符s.charAt(right),收缩,
set.remove(s.charAt(left));//移除,左边框,
// 一直有就一直移除
left++;
}
}
return maxLength;
}
}
经典题目:最小覆盖子串:76
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
count=3;
以本例本方法来分析:
need[65]=1; need[66]=1;need[67]=1;
window[68]=1,w[o]=1;w[66]=1;w[69]=1;w[67]=1;
count=2;
length=6;
显然,也是一道滑动窗口的题目
但是小窗口中的值相对窗口是乱序的,就需要我们把每次窗口中的值用数组或者哈希表收集起来
package Train.滑动窗口;
public class 最小覆盖子串 {
public String minWindow(String s, String t) {
// 问题:如何从字符串中找到目标子串
// 滑动窗口
// 方法:整型数组存放 Char, Char 的 int 值范围为 0 ~ 127,因为只有英文字母
if(s.length() == 0 || t.length() == 0 || s.length() < t.length()){
return ""; // 如果字符串为空,或者长度小于需要匹配的长度
}
int[] need = new int[128];// 利用数组 need 存放匹配子串中需要的字符个数
int[] window = new int[128]; // 利用数组 window 存放窗口中字符个数
// 窗口中已经匹配的字符个数
int count = 0;
int left = 0;
int right = 0;
int minLength = s.length();
String minString = "";
// need 初始化,统计字符串t中每个字符的出现次数,并将统计结果保存在数组need中.
for(int i = 0; i < t.length(); i ++ ){
need[t.charAt(i)] ++;
}
while(right < s.length()){
char ch = s.charAt(right);
window[ch] ++;
// 如果需要该字符,并且已有窗口内的字符个数 小于需要的字符个数
if(need[ch] > 0 && need[ch] >= window[ch]){
count ++;
}
// 当需要的字符都已经包含在窗口中后,开始收缩 left
while(count == t.length()){
ch = s.charAt(left);
// 当需要删除的字符,是必须留在窗口内时
if(need[ch] > 0 && need[ch] == window[ch]){
count --;
}
// 这边需要取 = ,因为可能一开始两个字符串就是匹配的,如 a , a return a
if(right - left + 1 <= minLength){
minLength = right - left + 1;
minString = s.substring(left, right + 1);
}
window[ch] --;
left ++;
}
right ++;
}
return minString;
}
}