文章目录
滑动窗口非常适用于`子串(例如字符串以及数组这种
在两个子串中进行查找元素,在一个串中查找元素
框架
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
Map<char, int> need, window;
// 将待比较的字符串中的字符以及字符出现的次数
for(int i =0;i<p.length();i++) {
need.put(p.charAt(i),need.getOrDefault(p.charAt(i),0)+1);
}
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s.charAt(right);
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s.charAt(left);
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
两个串(序列的)情形
- 若是两个字符串进行比较,则可以定义
hashMap
,每个hashMap
的key存储串
中的元素,value
存储串中元素出现的次数(这样定义的好处是为了方便对重复元素的各种处里,往往需要定义一个valid变量用来判断是否各个可能存在多个的重复数量的元素是否在窗口中满足了要求例如aaabcdd
)- 首先将目标串遍历将其放入到
need
中- 遍历查找串,在
right<s.size
的情况下进行遍历,首先查找到满足条件的一个清醒,然乎不断缩短窗口- `注意是子串还是子序列的问题
一个串的情形
- 定义一个hashMap即可
- 遍历这个串,序列即可
LeetCode76 最小覆盖子串
package com.zj.GWindow;
import com.zj.Aaaray.Problem1;
import java.util.HashMap;
import java.util.Map;
/**
* @Author Zhou Jian
* @Date 2020/8/12
*/
public class Problem76 {
/**
* 最小覆盖子串
* 给一个字符串S,一个字符串T,请在字符串S里找出:包含T所有字母的最小子串
* @param s
* @param t
* @return
* 1、我们在字符串S中使用双指针中的左右指针技巧,初始化left=right=0,把索引左闭右开区间[left,right)称为一个[窗口]
* 2、我们先不断增加right指针扩大窗口[left,right),直到窗口中的字符串符合要求(包含了T中的所有字符)
* 3、此时,我们停止增加right,转而不断增加left指针缩小窗口[left,right)直到窗口中的字符串不再符合要求(不包含T中的所有字符(
* 同时,每次增加left,我们都要更新一轮结果
* 4、重复2和第3步,直到rigjt到达字符春S的尽头
*
*/
public String minWindow(String s, String t) {
// 初始化两个hash表,记录窗口中的字符和需要凑齐的字符,包括要凑齐的字符数
Map<Character,Integer> need = new HashMap<>();
Map<Character,Integer> window = new HashMap<>();
// 对need Map进行更新
for(int i=0;i<t.length();i++){
need.put(t.charAt(i),need.getOrDefault(t.charAt(i),0)+1);
}
// 使用left和right变量初始化窗口的两端
// 不要忘了,区间[left,right)树左闭右开的
int left =0;
int right = 0;
// valid变量表示窗口中满足need条件的字符的个数(是hashMap中字符对应的字符的个数)
int valid = 0;
//记录最小覆盖子串的其实索引和长度
int start =0;
int end = 0;
int len = Integer.MAX_VALUE;
while(right<s.length()){
// c是将移入窗口中的字符
Character c = s.charAt(right);
//右移动窗口
right++;
// 进行窗口内数据的一系列更新,找到可行解
if(need.containsKey(c)){
window.put(c,window.getOrDefault(c,0)+1);
if(need.get(c).compareTo(window.get(c))==0){
valid++;
}
}
// 判断左侧窗口是否要收缩
while (valid==need.size()){
//在这里更新最小覆盖子串
if(right-left<len){
start=left;
end = right;
len = right-left;
}
// d是将移除窗口的字符
Character d = s.charAt(left);
//窗口左移动
left++;
//进行窗口内数据的更新,需要这个数
if(need.containsKey(d)){
//窗口内这个数可以移除
if(window.get(d).equals(need.get(d)))
valid--;
window.put(d,window.get(d)-1);
}
}
}
// 返回最小覆盖子串
return len == Integer.MAX_VALUE?
"" : s.substring(start,end);
}
public String minWindow1(String s, String t) {
// 初始化两个hash表,记录窗口中的字符和需要凑齐的字符,包括要凑齐的字符数
Map<Character,Integer> need = new HashMap<>();
Map<Character,Integer> window = new HashMap<>();
for(int i=0;i<t.length();i++){
need.put(t.charAt(i),need.getOrDefault(t.charAt(i),0)+1);
}
// 使用left和right变量初始化窗口的两端
// 不要忘了,区间[left,righjt)树左闭右开的
int left =0;
int right = 0;
int valid = 0; // valid变量表示窗口中满足nee条件的字符的个数
//记录最小覆盖子串的其实索引和长度
int start =0;
int end = 0;
int len = Integer.MAX_VALUE;
while(right<s.length()){
// c是将移入窗口中的字符
Character c = s.charAt(right);
//右移动窗口
right++;
// 进行窗口内数据的一系列更新
if(need.containsKey(c)){
window.put(c,window.getOrDefault(c,0)+1);
if(need.get(c).compareTo(window.get(c))==0){
valid++;
}
}
// 判断左侧窗口是否要收缩
while (valid==need.size()){
//在这里更新最小覆盖子串
if(right-left<len){
start=left;
end = right;
len = right-left;
}
// d是将移除窗口的字符
Character d = s.charAt(left);
//窗口左移动
left++;
//进行窗口内数据的更新,需要这个数
if(need.containsKey(d)){
//窗口内这个数可以移除
if(window.get(d).equals(need.get(d)))
valid--;
window.put(d,window.get(d)-1);
}
}
}
// 返回最小覆盖子串
return len == Integer.MAX_VALUE?
"" : s.substring(start,end);
}
public static void main(String[] args) {
String s = "ADOBECODEBANC";
String t = "ABC";
Problem76 problem76 = new Problem76();
String minWindow = problem76.minWindow1(s, t);
System.out.println(minWindow);
}
}
LeetCode28 字符串的查找indexOf
- 注意这道题目和下体的区别
- 这道图球的是字符串子串
- 下到题目球的是字符串的排列(不好用)
- 可以尝试使用KMP算法
public int strStr(String haystack, String needle) {
int length = needle.length();
for(int i=0;i<=haystack.length()-length;i++){
if(haystack.substring(i,i+length).equalsIgnoreCase(needle)) return i;
}
return -1;
}
采用滑动窗口的方法
/**
* 滑动窗口方法
* @param haystack
* @param needle
* @return
* 给定一个 haystack 字符串和一个 needle 字符串,
* 在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。
* 如果不存在,则返回 -1。
* 这种求的是序列
*/
public int strStr(String haystack, String needle) {
if(needle==null) return -1;
if(needle.equals("")) return 0;
Map<Character,Integer> need = new HashMap<>();
Map<Character,Integer> window = new HashMap<>();
for(int i=0;i<needle.length();i++){
need.put(needle.charAt(i),need.getOrDefault(needle.charAt(i),0)+1);
}
int left = 0;
int right = 0;
int valid = 0;
while (right<haystack.length()){
// 不断扩大窗口
Character c = haystack.charAt(right);
right++;
if(need.containsKey(c)){
window.put(c,window.getOrDefault(c,0)+1);
if(window.get(c).equals(need.get(c))) valid++;
if(valid==need.size()) return left;
}
while((right-left)>=needle.length()){
Character d = haystack.charAt(left);
left++;
if(need.containsKey(d)) {
if (window.get(d).equals(need.get(d))) valid--;
window.put(d, window.get(d) - 1);
}
}
}
return -1;
}
LeetCode567字符串的排列
package com.zj.GWindow;
import com.zj.Aaaray.Problem1;
import java.util.HashMap;
import java.util.Map;
/**
* @Author Zhou Jian
* @Date 2020/8/13
字符串排列
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
*/
public class Problem567 {
/**
* need s1
* window 划过s2
* @param s1
* @param s2
* @return
* 套用模板s=
* 注意哦,输入的s1是可以包含重复字符的,所以这个题难度不小。
*
* 这种题目,是明显的滑动窗口算法,相当给你一个S和一个T,请问你S中是否存在一个子串,包含T中所有字符且不包含其他字符?
*
* 首先,先复制粘贴之前的算法框架代码,然后明确刚才提出的 4 个问题,即可写出这道题的答案:
*/
public boolean checkInclusion(String s1, String s2) {
Map<Character,Integer> need = new HashMap<>();
Map<Character,Integer> window = new HashMap<>();
for(int i=0;i< s1.length();i++){
need.put(s1.charAt(i),need.getOrDefault(s1.charAt(i),0)+1);
}
int left = 0;
int right = 0;
int valid = 0;
while (right<s2.length()){
Character c = s2.charAt(right);
right++;
//将遍历的数据加入到窗口
window.put(c,window.getOrDefault(c,0)+1);
if(need.containsKey(c)&&(window.get(c).compareTo(need.get(c))==0)){
valid++;
}
// 如果满足
if(valid==need.size()) return true;
// 超出s1的长度需要移除一个元素
if(right-left>=s1.length()){
Character d = s2.charAt(left);
left++;
if(need.containsKey(d)&&(window.get(d).compareTo(need.get(d))==0)){
valid--;
}
window.put(d,window.get(d)-1);
}
}
return false;
}
public static void main(String[] args) {
String s1 = "ab";
String s2 = "eidboaoo";
Problem567 problem567 = new Problem567();
System.out.println(problem567.checkInclusion(s1, s2));
}
}
LeetCode438 找到字符串中所有字母异位词
package com.zj.GWindow;
import com.zj.Aaaray.Problem1;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author Zhou Jian
* @Date 2020/8/13
* 找到字符串中所有字母亦为此
*/
public class Problem438 {
/**
* 套用模板
* @param s
* @param p
* @return
*/
public List<Integer> findAnagrams(String s, String p) {
List<Integer> rs = new ArrayList<>();
Map<Character,Integer> need = new HashMap<>();
Map<Character,Integer> window = new HashMap<>();
for(int i =0;i<p.length();i++) {
need.put(p.charAt(i),need.getOrDefault(p.charAt(i),0)+1);
}
int left = 0;
int right = 0;
int valid = 0;
while (right<s.length()){
Character c = s.charAt(right);
right++;
window.put(c,window.getOrDefault(c,0)+1);
if(need.containsKey(c)&&(window.get(c).compareTo(need.get(c)))==0){
valid++;
}
if(valid==need.size()) {
rs.add(left);
}
System.out.println(left+"---"+right);
// 所小窗口,将left所在位置移除窗口,移除窗口之前先需要对数据进行判断
if (right-left>=p.length()){
Character d = s.charAt(left);
left++;
if((need.containsKey(d))&&(window.get(d).compareTo(need.get(d)))==0){
valid--;
}
window.put(d,window.get(d)-1);
}
}
return rs;
}
public static void main(String[] args) {
Problem438 problem438 = new Problem438();
System.out.println(problem438.findAnagrams("baa", "aa"));
}
}
LeetCode3 最长无重复子串
/**
* 无重复的最长子串
* 采用滑动窗口的方法
* @param s
* @return
*/
public int lengthOfLongestSubstring(String s) {
// 这就相当于一个窗口,
// 其实就是一个缓存,缓存中记录窗口内的每个值以及每个值出现的次数
Map<Character,Integer> map = new HashMap<>();
// 窗口的边界
int left = 0;
int right = 0;
int max = 0;
while (right<s.length()){
Character c = s.charAt(right);
right++;
// 计算窗口内每个值以及每个值出现的次数
map.put(c,map.getOrDefault(c,0)+1);
// 判断是否需要收缩窗口,
// 若添加完这个值之后串口内的个数大于1则需要不断移动左窗口的边界值
// 直到窗口的遏制小于1
while (map.get(c)>1){
Character d = s.charAt(left);
left++;
map.put(d,map.get(d)-1);
}
max = Math.max(right-left,max);
}
return max;
}
LeetCode239 滑动窗口内的最大值
滑动窗口
:使用一个队列(队列中存放的是数组中元素的索引值
),确保队首元素是滑动窗口内的最大值,如何保证,每添加一个元素要判断,这个元素是否大于队列中现有元素,若大于则不断移除队列中的元素,再判断队列中的元素是否不再此时滑动窗口范围内
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums == null || nums.length < 2) return nums;
// 双向队列 保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序
LinkedList<Integer> queue = new LinkedList();
// 结果数组
int[] result = new int[nums.length-k+1];
// 遍历nums数组,
for(int i = 0;i < nums.length;i++){
// 保证从大到小 如果前面数小则需要依次弹出,直至满足要求
while(!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]){
queue.pollLast();
}
// 添加当前值对应的数组下标
queue.addLast(i);
// 判断当前队列中队首的值是否有效
if(queue.peek() <= i-k){
queue.poll();
}
// 当窗口长度为k时 保存当前窗口中最大值
if(i+1 >= k){//队首元素就是该动态窗口的最大值
result[i+1-k] = nums[queue.peek()];
}
}
return result;
}
}