Java-数据结构-滑动窗口

一. 滑动窗口的简单介绍

        滑动窗口是双指针技巧的一种,常用于解决子串、子序列问题。滑动窗口的思想是维护一个窗口,不断滑动更新。滑动窗口的难点是各种细节:如何向窗口中添加元素、如何缩小窗口、何时更新结果。

        滑动窗口有一套通用的框架,解决滑动窗口题目大家都可以尝试套用该框架。框架的整体思路是移动窗口右边界,向窗口中添加元素,窗口满足要求解的问题(如窗口等于目标子串),开始滑动左边界找到满足条件的最小值。

        图来自【2】(参考来源)

Java-数据结构-滑动窗口_第1张图片

 Java-数据结构-滑动窗口_第2张图片

二. 滑动窗口的代码模板&要点

代码模板

public void slidingWindow(String str,String target){
    //用哈希表存储当前窗口中每个字符出现次数
    // need:存储待查找子串每个字符出现次数
    Map need,window = new HashMap();
    //窗口左右边界
    int left = 0;
    int right = 0;
    while(right

要点

(1)窗口的左右边界

        left和right分别负责滑动窗口的左右边界,那么有时这个窗口是固定的,有时则是不固定长度,会需要求最小长度。

(2)什么时候right++

        基本在窗口内,right是每轮都需要++的,可以通过continue提前跳过本轮,减少一丢时间

(3)什么时候left++

        在达到一定的条件后就需要收缩窗口了,若是固定窗口长度,泽到达固定长度后收缩,若是求最小长度,则一直收缩到窗口内的值不满足条件为止

(4)什么时候更新res

        res最后的返回答案,可以在每轮right++后进行更新,也可以在left++后更新,那么有时最后一轮right

三. 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 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 findAnagrams(String s, String p) {
        int[] ori = new int[26];
        int[] win = new int[26];
        int left = 0;
        int right = 0;
        List 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 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 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 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

你可能感兴趣的:(leetcode,Java,java,数据结构,leetcode)