数据结构与算法-滑动窗口练习

长度最小的子数组(209)

方法一:暴力求解(效率差):
使用两个 for 循环,一个 for 循环固定一个数字比如 m,另一个 for 循环从 m 的下一个元素开始累加,当和大于等于 s 的时候终止内层循环,顺便记录下最小长度


    public int minSubArrayLen(int s, int[] nums) {
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < nums.length; i++) {
            int sum = nums[i];
            if (sum >= s)
                return 1;
            for (int j = i + 1; j < nums.length; j++) {
                sum += nums[j];
                if (sum >= s) {
                    min = Math.min(min, j - i + 1);
                    break;
                }
            }
        }
        return min == Integer.MAX_VALUE ? 0 : min;
    }

方法二:滑动窗口,使用双指针,left和right,分别用来向右缩小窗口和向右扩大窗口
思路:遇到题目求最小,可思考先预设一个最大值,然后不断更新
窗口包含数值总和大了就向右缩小窗口,小了就向右扩展窗口,不断试错,直至找到最小窗口,并且使所有值加起来超不过target
注意前后顺序

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;       
        int sum = 0;
        int result = Integer.MAX_VALUE;//让窗口最大,这样就能不断更新最小值窗口
        for(int right = 0;right < nums.length ;right++){
            sum += nums[right];
            while(sum >= target){
                
                result = Math.min(result, right - left + 1);//让窗口永远保持最小
                sum -= nums[left++];//左侧索引前移
                
                
            }           
        }
        //依照案例,如果所有值加起来都小于target,返回0,也就是MAX_VALUE一直都没变过
        return result == Integer.MAX_VALUE ? 0 : result;

    }
}

找到字符串中所有字母异位词(438)

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> ans = new ArrayList<>();
        int m = s.length();
        int n = p.length();

        if (m < n) {
            // 如果s没p长,直接返回空列表
            return ans;
        }

        int[] sCnt = new int[26];
        int[] pCnt = new int[26];
        
        for (int i = 0; i < n; i++) {
            sCnt[s.charAt(i) - 'a']++;
            pCnt[p.charAt(i) - 'a']++;
        }

        if (Arrays.equals(sCnt, pCnt)) {
            // 初始窗口就相等
            ans.add(0);
        }

        for (int i = 0; i < m - n; i++) {
            // 窗口右移
            sCnt[s.charAt(i) - 'a']--;
            sCnt[s.charAt(i + n) - 'a']++;

            if (Arrays.equals(sCnt, pCnt)) {
                ans.add(i + 1);
            }
        }

        return ans;
    }
}

最小覆盖子串(76)

本题考察的算法思想时滑动窗口,对于滑动窗口,需要考虑窗口什么时候扩大、什么时候缩小窗口、什么时候结算窗口内的信息、窗口扩大及缩小的对应操作。
首先遍历t字符串,统计每个字母出现的频率。
定义valid代表已完成匹配字符的数量
r 为窗口右边界,l 代表窗口左边界
开始遍历 s 中的每个字符,
若当前字符在 t 中出现过,window 将这个字符收入,对应词频+1,
若window 与 need 中的这个字符频率相等,valid++,代表完成一个字符的匹配
若某时valid==need.size(),代表已经完成了匹配,需要结算窗口的信息
信息包含左右边界的位置,以及此时最小覆盖的字串长度是多少
r-l 代表此时窗口的大小, r-l < len 代表此时窗口小于上一次的窗口,
此时更新 要返回的子字符串的开始与结束位置
start = l , l 代表窗口左边界,将这种情况下的左边界赋值给 l
len = r-l , len 代表窗口大小
由于题目要求的是最小的包含 t 字符的字符串,所以需要不断缩小窗口来求出最小的窗口大小
need.containsKey( d ) 判断当前 l 位置的字符是否是need需要的,就是t中是否包含的字符
若window 与 need 中的这个字符频率相等,valid–,下一次重新开始匹配
window 需要将这个字符移除
若这个字符不在 need 中,也就是不需要的字符,则继续判断下一个左边界代表的字符。
最后返回之前判断len是否等于 Integer.MAX_VALUE
如果等于,则代表没能匹配成功,
如果不等,代表上面过程中有匹配成功的情况,而在上面过程中,我们总是不断缩小窗口大小,且每次都将左窗口赋值给 start , len代表着完成匹配的窗口大小。
所以最后截取的字符串为 s.substring(start, start + len) 。
此解法仅在匹配过程中线性扫描两个字符串,所以整体时间复杂度为O(n)。

 public String minWindow( String s, String t ) {
        String res = "";
        if ( t.length() > s.length() ) {
            return res;
        }

        HashMap<Character, Integer> need = new HashMap<>();
        HashMap<Character, Integer> window = new HashMap<>();

        for ( int i = 0; i < t.length(); i++ ) {
            char ch = t.charAt( i );
            need.put( ch, need.getOrDefault( ch, 0 ) + 1 );
        }
        int l = 0;
        int r = 0;
        int valid = 0;
        int len = Integer.MAX_VALUE;
        int start = 0;
        while ( r < s.length() ) {
            char ch = s.charAt( r );
            r++;
            if ( need.containsKey( ch ) ) {
                window.put( ch, window.getOrDefault( ch, 0 ) + 1 );
                if ( window.get( ch ).equals( need.get( ch ) ) ) {
                    valid++;
                }
            }
            // 判断左侧窗口是否要收缩
            while ( valid == need.size() ) {
                // 在这里更新最小覆盖子串
                if ( r - l < len ) {
                    start = l;
                    len = r - l;
                }
                // d 是将移出窗口的字符
                char d = s.charAt( l );
                // 左移窗口
                l++;
                // 进行窗口内数据的一系列更新
                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, start+len );
    }

字符串的排列(567)

要注意什么时候向右扩大窗口,什么时候向右缩小窗口,什么时候结算窗口内的信息,如何更新窗口内的信息

class Solution {
    public boolean checkInclusion(String s1, String s2) {
        int l=0;
        int r=0;
        
        int []s1arr=new int[26];
        int []s2arr=new int[26];
        for(int i=0;i<s1.length();i++){
            s1arr[s1.charAt(i)-'a']++;
        }
        while(r<s2.length()){
            s2arr[s2.charAt(r)-'a']++;
            
            if(r-l+1==s1.length()){
               if(equalS1S2(s1arr,s2arr)) {
                   return true;
               }else{
                s2arr[s2.charAt(l)-'a']--;
                l++;
               }  
            }
            r++;//r++位置要放好,不能直接放在s2arr[s2.charAt(r)-'a']++;下面
        }
        return false;
    }
    public  boolean equalS1S2( int[] s1,int[] s2){
        for(int i=0;i<s1.length;i++){
            if(s1[i]!=s2[i])return false;     
        }
        return true;

    }
}

无重复字符的最长子串(3)

使用set,让left每次加一

/*时间复杂度:O(2n) = O(n),最坏的情况是 left 和 right 都遍历了一遍字符串*/
	/*空间复杂度:O(n)*/
	/*https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/jian-dan-yi-dong-javac-pythonjshua-dong-bff20/*/
	public int lengthOfLongestSubstring2(String s) {
	    int n = s.length();
	    if (n <= 1) return n;
	    int maxLen = 1;

	    int left = 0, right = 0;
	    /*使用set集合记录窗口中出现的字符,这样可以保证 不重复,
	     *如果窗口中不包含右指针指向的字符,就把它加入set(并记录窗口最大值 ),如果存在就移除 ,并将left指针++,每次移动一次*/
	    Set<Character> window = new HashSet<>();
	    while (right < n) {
	        char rightChar = s.charAt(right);
	        while (window.contains(rightChar)) {
	            window.remove(s.charAt(left));
	            left++;
	        }
	        maxLen = Math.max(maxLen, right - left + 1);
	        window.add(rightChar);
	        right++;
	    }

	    return maxLen;
	}

使用map,记录每个字符的索引,遇到重复字符时,直接跳到 重复字符的下一个 位置

/*上面的方法,出现重复字符时,是一个一个字符的缩小窗口
*可以通过map记录每个字符在字符串中出现的位置,当遇到重复字符时,把left指针跳到重复字符的下一个位置 */
/*/*力扣3,评论区
 * https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/wu-zhong-fu-zi-fu-de-zui-chang-zi-chuan-by-leetc-2
 * */*/
	 public static int lengthOfLongestSubstringWithoutRepeatingCharacters(String s) {
	        int len = s.length();

	        Map<Character, Integer> index = new HashMap<>();

	        int max = 0;
	        int left = 0;
	        for (int right = 0; right < len; right++) {
	            char c = s.charAt(right);
	            if (index.containsKey(c)) {
	                int lastIndex = index.get(c);
	                if (lastIndex + 1 > left) {
	                    left = lastIndex + 1;
	                }
	            }
	            //因为左指针已经跳到重复字符的下一个位置了,当前右指针指向的字符跟去掉的字符一样,所以需要在map中更新一下位置	
	            index.put(c, right);
	            max = Math.max(max, right - left + 1);
	        }

	        return max;
	    }
	 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值