数组双指针---滑动窗口

序言:

遇到子数组/子串相关的问题,只要能回答出来以下几个问题,就能运用滑动窗口算法:

1、什么时候应该扩大窗口?

2、什么时候应该缩小窗口?

3、什么时候得到一个合法的答案?

一、最小覆盖字串

题目:给定一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

示例1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

示例2:

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

题1解题思路:

1、键值对:字符-->数量HashMap<char,int>

2、开两个窗口,need与windows窗口,首先初始化need窗口,记录目标串所需的所有字符以及对应的个数。

3、左右两个指针,left和right。首先right向右移动,直到匹配到need窗口内的所有字符,然后开始移动left指针缩小窗口,并更新 len 长度。

class Solution {
    public String minWindow(String s, String t) {
        HashMap<Character,Integer> need = new HashMap<Character, Integer>();
        HashMap<Character,Integer> windows = new HashMap<Character, Integer>();
        for(int i=0;i<t.length();i++){
            char c = t.charAt(i);
            need.put(c,need.getOrDefault(c,0)+1);
        }
        int left = 0 , right = 0;
        int valid = 0;
        int start = 0 , len = Integer.MAX_VALUE;
		
        //移动right指针直到指针到达终点
        while(right<s.length()){
            //字符加入窗口操作****
            char rc = s.charAt(right);
            right++;
            if(need.containsKey(rc)){
                windows.put(rc,windows.getOrDefault(rc,0)+1);
                if(windows.get(rc).equals(need.get(rc))) valid++; //这句很重要!!!
            }
            //开始移动left指针,缩小窗口
            while(valid == need.size()){
                //判断是否需要缩小窗口
                if(right-left<len){
                    start = left;
                    len = right-left;
                }
                //字符弹出窗口操作****
                char lc = s.charAt(left);
                left++;
                if(need.containsKey(lc)){
                    if(windows.get(lc).equals(need.get(lc))) valid--;//这句很重要!!!
                    windows.put(lc,windows.getOrDefault(lc,0)-1);
                }
            }
        }

        return len==Integer.MAX_VALUE ? "":s.substring(start,start+len);
    }
}

二、字符串排列

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

示例 1:

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

示例 2:

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

题2解题思路:

一开始简单的以为:只需要一个need字典,将s1有的字符一股脑放进need字典里,在s2 维护右指针看看有没有符合的字串就行,每当遇到非法字符,right直接跳到当前位置下一个。但这样是不对的:第一、没有考虑合法字符重复的问题;第二、由于没有考虑到第一点,所以也没有考虑到【合法字符数量超过导致匹配失败后】应该从起始位置下一个字符开始重新遍历!

class Solution{
    public boolean checkInclusion(String s1,String s2){
        HashMap<Character,Integer> need = new HashMap<>();
        HashMap<Character,Integer> windows = new HashMap<>();
        for(int i=0;i<s1.length();i++){
            char c = s1.charAt(i);
            need.put(c,need.getOrDefault(c,0)+1);
        }
        int left=0,right=0;
        int valid=0;
        while(right<s2.length()){
            char rc = s2.charAt(right);
            right++;
            if(need.containsKey(rc)){
                windows.put(rc,windows.getOrDefault(rc,0)+1);
                if(windows.get(rc).equals(need.get(rc))) valid++;
            }
            if(right-left>s1.length()){
                char lc = s2.charAt(left);
                left++;
                if(need.containsKey(lc)){
                    if(windows.get(lc).equals(need.get(lc))) valid--;
                    windows.put(lc,windows.get(lc)-1);
                }
            }
            if(valid==need.size()) return true;  //放在这里因为窗口大小一定得合法!!
        }
        return false;
    }
}

三、找所有字母异位词

题目:给定两个字符串 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" 的异位词。

题3解题思路:

这题就是上面第二题的升级版,这个所谓的字母异位词,不就是排列吗,搞个高端的说法就能糊弄人了吗?

class Solution{
    public List<Integer> findAnagrams(String s, String p){
        List<Integer> list = new ArrayList<>();
        HashMap<Character,Integer> need = new HashMap<>();
        HashMap<Character,Integer> windows = new HashMap<>();
        for(int i=0;i<p.length();i++){
            char c = p.charAt(i);
            need.put(c,need.getOrDefault(c,0)+1);
        }
        int left=0,right=0;
        int res=0;
        int valid=0;
        while(right<s.length()){
            char rc = s.charAt(right);
            right++;
            if(need.containsKey(rc)){
                windows.put(rc,windows.getOrDefault(rc,0)+1);
                if(windows.get(rc).equals(need.get(rc))) valid++;
            }
            if(right-left>p.length()){  //注意是目标子串的长度!
                char lc = s.charAt(left);
                left++;
                res=left;
                if(need.containsKey(lc)){
                    if(windows.get(lc).equals(need.get(lc))) valid--;
                    windows.put(lc,windows.getOrDefault(lc,0)-1);
                }
            }
            if(valid==need.size()) list.add(res);
        }
        return list;
    }
}

四、无重复字符的最长子串

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

示例1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例2:

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

题4解题思路:

class Solution{
    public int lengthOfLongestSubstring(String s) {
        HashMap<Character,Integer> windows = new HashMap<>();
        int left=0,right=0;
        int len=0;
        while(right<s.length()){
            char rc = s.charAt(right);
            right++;
            windows.put(rc,windows.getOrDefault(rc,0)+1);
            while(windows.get(rc)>1){
                char lc = s.charAt(left);
                left++;
                windows.put(lc,windows.getOrDefault(lc,0)-1);
            }
            if(right-left>len) len = right-left;
        }
        return len;
    }
}

五、滑动窗口的最大值

题目:给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 
  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

题4解题思路:

  • 特殊数据结构:单调队列(monotonicQueue

在⼀堆数字中,已知最值,如果给这堆数添加⼀个数,那么比较⼀下就可以很快算出最值;但如果减少⼀个数,就不一定能很快得到最值了,而是要遍历所有数重新找最值。 回到这道题的场景,每个窗口前进的时候,要添加⼀个数同时减少⼀个数, 所以想在 O(1) 的时间得出新的最值,就需要「单调队列」这种特殊的数据结构来辅助了。

class MonotonicQueue {
    void push(int n); //在队尾添加元素n 
    int max(); //返回当前队列中的最⼤值
    void pop(int n);  //队头元素如果是n,删除它 
}

单调队列的 push 方法依然在 队尾添加元素,但是要把前面比新元素小的元素都删掉:相当于加入数字的大小代表人的体重,把前面体重不足的都压扁了,直到遇到更大的量级才停住。

基于上述,pop方法就是弹出队头元素,但是要加判断data.front() == n,因为我们想删除的队头元素 n 可能已经被「压扁」了,如果被「压扁」了就不用再进行删除操作,因为该元素已经不在队列里面了。

class Solution{
    class monotonicQueue{
        LinkedList<Integer> list = new LinkedList<>();
        //在队尾添加元素n,重要!!!
        public void push(int n){
            while(!list.isEmpty()&&list.getLast()<n){
                list.pollLast();
            }
            list.addLast(n);
        }
        //返回当前队列中的最⼤值
        public int max(){
            return list.getFirst();
        }
		//队头元素如果是n,删除它; 若不是,则代表该元素已经被“压扁”了,不用进行删除操作
        public void pop(int n){
            if(list.getFirst()==n) list.pollFirst();
        }
    }

    public int[] maxSlidingWindow(int[] nums, int k) {
        monotonicQueue window = new monotonicQueue();
        List<Integer> res = new ArrayList<>();
        for(int i=0;i<nums.length;i++){
            if(i<k-1){
                window.push(nums[i]);
            }else{
                window.push(nums[i]);
                res.add(window.max());
                window.pop(nums[i - k + 1]);
            }
        }
        int[] ans = new int[res.size()];
        for(int i=0;i<res.size();i++){
            ans[i]=res.get(i);
        }
        return ans;
    }
}
posted @   JH_KingHau  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示