Java-数据结构-滑动窗口
一. 滑动窗口的简单介绍
滑动窗口是双指针技巧的一种,常用于解决子串、子序列问题。滑动窗口的思想是维护一个窗口,不断滑动更新。滑动窗口的难点是各种细节:如何向窗口中添加元素、如何缩小窗口、何时更新结果。
滑动窗口有一套通用的框架,解决滑动窗口题目大家都可以尝试套用该框架。框架的整体思路是移动窗口右边界,向窗口中添加元素,窗口满足要求解的问题(如窗口等于目标子串),开始滑动左边界找到满足条件的最小值。
图来自【2】(参考来源)
二. 滑动窗口的代码模板&要点
代码模板
-
public void slidingWindow(String str,String target){
-
//用哈希表存储当前窗口中每个字符出现次数
-
// need:存储待查找子串每个字符出现次数
-
Map<Character,Integer> need,window = new HashMap();
-
//窗口左右边界
-
int left = 0;
-
int right = 0;
-
while(right<target.length()){
-
char c1 = str.charAt(right) //待加入窗口的字符
-
right++; //右滑窗口
-
//更新窗口信息,即哈希表状态
-
//满足窗口左滑条件
-
while(){
-
char c2 = s.charAt(left);
-
left++;
-
//更新窗口信息,即哈希表状态
-
}
-
}
-
}
要点
(1)窗口的左右边界
left和right分别负责滑动窗口的左右边界,那么有时这个窗口是固定的,有时则是不固定长度,会需要求最小长度。
(2)什么时候right++
基本在窗口内,right是每轮都需要++的,可以通过continue提前跳过本轮,减少一丢时间
(3)什么时候left++
在达到一定的条件后就需要收缩窗口了,若是固定窗口长度,泽到达固定长度后收缩,若是求最小长度,则一直收缩到窗口内的值不满足条件为止
(4)什么时候更新res
res最后的返回答案,可以在每轮right++后进行更新,也可以在left++后更新,那么有时最后一轮right<nums.length时跳出循环,注意此时是否还需要一轮比较
三. leetcode实战
1. leetcode3 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
Boolean数组
-
class Solution {
-
public int lengthOfLongestSubstring(String s) {
-
if(s == null){
-
return 0;
-
}
-
boolean[] arr = new boolean[128];
-
char[] ch = s.toCharArray();
-
int left = 0;
-
int right = 0;
-
int dis = 0;
-
int max =0;
-
while(right < ch.length){
-
if(arr[ch[right]] == false){
-
arr[ch[right]] = true;
-
right++;
-
continue;
-
}
-
dis = right-left;
-
max = max > dis? max : dis;
-
while(arr[ch[right]] == true){
-
arr[ch[left]] = false;
-
left++;
-
}
-
}
-
dis = right-left;
-
max = max > dis? max : dis;
-
return max;
-
}
-
}
HashMap
-
class Solution {
-
public int lengthOfLongestSubstring(String s) {
-
if(s == null || s.length() == 0){
-
return 0;
-
}
-
HashMap<Character,Integer> map = new HashMap<>();
-
int left = 0;
-
int right = 0;
-
int max = 0;
-
while(right < s.length()){
-
char c = s.charAt(right);
-
map.put(c,map.getOrDefault(c,0)+1);
-
while(map.get(c) > 1){
-
char leftc = s.charAt(left);
-
map.put(leftc,map.get(leftc)-1);
-
left++;
-
}
-
max = Math.max(max,right-left+1);
-
right++;
-
}
-
return max;
-
}
-
}
int数组
-
class Solution {
-
public int lengthOfLongestSubstring(String s) {
-
int left = 0;
-
int right = 0;
-
int diff = 0;
-
int max = 0;
-
int len = s.length();
-
int[] nums = new int[256];
-
while(right < len){
-
if(nums[s.charAt(right)] == 0){
-
nums[s.charAt(right)]++;
-
right++;
-
continue;
-
}
-
diff = right-left;
-
max = Math.max(max, diff);
-
while(nums[s.charAt(right)] > 0){
-
nums[s.charAt(left)]--;
-
left++;
-
}
-
}
-
diff = right-left;
-
max = Math.max(max, diff);
-
return max;
-
}
-
}
本题小结:(1)注意在最后还有一次比较
(2)对于伪HashSet可以用boolean数组代替
(3)一般达到条件后left左移都是用while循环,达到一个条件后退出
2. leetcode438 找到字符串中所有字母异位词
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
-
class Solution {
-
public List<Integer> findAnagrams(String s, String p) {
-
int[] ori = new int[26];
-
int[] win = new int[26];
-
int left = 0;
-
int right = 0;
-
List<Integer> list = new ArrayList<>();
-
if(s.length() < p.length()) return list;
-
for(int i = 0; i < p.length(); i++){
-
ori[p.charAt(i)-'a']++;
-
}
-
int len = p.length();
-
while(right < s.length()){
-
char c = s.charAt(right);
-
win[c-'a']++;
-
while(win[c-'a'] > ori[c-'a']){
-
win[s.charAt(left)-'a']--;
-
left++;
-
}
-
if(right - left +1 == len){
-
list.add(left);
-
}
-
right++;
-
}
-
return list;
-
}
-
}
本题小结:(1)注意边界位置right - left +1
3. leetcode567 字符串的排列
给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。
换句话说,s1 的排列之一是 s2 的 子串 。
输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").
-
class Solution {
-
public boolean checkInclusion(String s1, String s2) {
-
if(s1.length() > s2.length()) return false;
-
int[] oristr = new int[26];
-
int[] win = new int[26];
-
int left = 0;
-
int right = 0;
-
for(int i = 0; i < s1.length(); i++){
-
oristr[s1.charAt(i)-'a']++;
-
}
-
while(right < s2.length()){
-
char c1 = s2.charAt(right);
-
win[c1 -'a']++;
-
while( win[c1 -'a'] > oristr[c1 -'a']){
-
char c2 = s2.charAt(left);
-
win[c2-'a']--;
-
left++;
-
}
-
if( right - left +1== s1.length()){
-
return true;
-
}
-
right++;
-
}
-
return false;
-
}
-
}
4. leetcode76 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
-
class Solution {
-
public String minWindow(String s, String t) {
-
if(s.length() < t.length()) return "";
-
int[] ori = new int[128];
-
int[] win = new int[128];
-
int right = 0;
-
int left = 0;
-
int res = s.length();
-
int count = 0;
-
String str = "";
-
for(int i = 0; i < t.length(); i++){
-
ori[t.charAt(i)]++;
-
}
-
while(right < s.length()){
-
char c = s.charAt(right);
-
win[c]++;
-
if(ori[c] >0 && win[c] <= ori[c]){
-
count++;
-
}
-
while(count == t.length()){
-
char cL = s.charAt(left);
-
if(ori[cL] > 0 && win[cL] <= ori[cL]){
-
count--;
-
}
-
if(right-left+1 <= res){
-
res = right-left+1;
-
str = s.substring(left,right+1);
-
}
-
left++;
-
win[cL]--;
-
}
-
right++;
-
}
-
return str;
-
}
-
}
本题小结:(1)以count来记录在滑动窗口内有效字符串个数,对其他无效的不给记录
5. leetcode219 存在重复元素 II
给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。
输入:nums = [1,2,3,1], k = 3
输出:true
HashMap
-
class Solution {
-
public boolean containsNearbyDuplicate(int[] nums, int k) {
-
HashMap<Integer,Integer> map = new HashMap<>();
-
int left = 0;
-
int right = 0;
-
while(right < nums.length){
-
map.put(nums[right],map.getOrDefault(nums[right],0)+1);
-
// map.put(c,map.getOrDefault(c,0)+1);
-
if(map.get(nums[right]) >= 2){
-
return true;
-
}
-
if(right - left == k){
-
map.put(nums[left],map.get(nums[left])-1);
-
left++;
-
}
-
right++;
-
}
-
return false;
-
}
-
}
HashSet
-
class Solution {
-
public boolean containsNearbyDuplicate(int[] nums, int k) {
-
HashSet<Integer> set = new HashSet<>();
-
for(int i = 0; i < nums.length; i++) {
-
if(set.contains(nums[i])) {
-
return true;
-
}
-
set.add(nums[i]);
-
if(set.size() > k) {
-
set.remove(nums[i - k]);
-
}
-
}
-
return false;
-
}
-
}
本题小结:(1)以HashSet的长度充当k的长度,大大简化空间和时间。
6. leetcode220 存在重复元素 III
给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。
如果存在则返回 true,不存在返回 false。
输入:nums = [1,2,3,1], k = 3, t = 0
输出:true
-
class Solution {
-
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
-
TreeSet<Long> set = new TreeSet<>();
-
for(int i = 0; i < nums.length; i++){
-
Long ceil = set.ceiling((long)nums[i]-t);
-
if(ceil != null && (long)(ceil-nums[i]) <= t){
-
return true;
-
}
-
set.add((long)nums[i]);
-
if(i >= k){
-
set.remove((long)nums[i-k]);
-
}
-
}
-
return false;
-
}
-
}
本题小结:(1)最后会出现大数,要用long,并且要判断null,所以注意Long
(2)ceiling返回大于等于那个数的最小数
(3)开始的ceiling要减去t
(4)remove去除的元素是最左边的那个,for循环去除即可
参考来源:【1】leetcode 滑动窗口
【2】CSDN 惊鸿只为卿 滑动窗口详解
【3】leetcode 官方题解 存在重复元素 III