算法通关村第十六关——滑动窗口经典问题(白银)

1 最长子串专题

1.1 无重复字符的最长子串

leetcode 3. 无重复字符的最长子串

思路:

  1. 什么情况下,left需要调整位置?当right的值,等于,left的值时。那么,我们需要记录这个值的数据结构。
  2. 选什么作为记录值的数据结构,还方便查询是否存在呐?使用key,value,也就是Hashmap,方便,好用。
  3. 如果遇到了存在的值,那么left怎么移动?每移动一次,判断一次是否还有相等的数即可
  4. 其他跟青铜里的最后一题大差不差
class Solution {
    public int lengthOfLongestSubstring(String s) {
        HashMap<Character, Integer> map = new HashMap<>();
        int left = 0;
        int len = 0;
        int res = 0;
        for(int right = 0; right < s.length(); right++){
            while(map.containsKey(s.charAt(right))){
                map.remove(s.charAt(left));
                left++;
            }
            map.put(s.charAt(right), 1);
            len = right - left + 1;
            res = Math.max(res, len);
        }
        return res;
    }
}

不过这样写之后有多余的计算,在while循环里面,一个个移动,那么怎样一次性直接将left移动到位置,也就是重复值的位置,的下一个位置。

我们用了key,value的数据结构,所以可以存key的时候,设置value为当前的位置,这样移动就可以直接找到位置

class Solution {
    public int lengthOfLongestSubstring(String s) {
    // 如果字符串s为空,则直接返回0
    if (s.length() == 0) {
        return 0;
    }
    
    // 创建一个HashMap对象,用于存储字符和其在字符串中最后一次出现的索引
    HashMap<Character, Integer> map = new HashMap<Character, Integer>();
    
    // 初始化变量max为0,表示最长不重复子串的长度
    int max = 0;
    
    // 初始化左指针left为0,表示子串的起始位置
    int left = 0;
    
    // 使用for循环遍历字符串s,从左到右逐个字符进行处理
    for (int right = 0; right < s.length(); right++) {
        // 如果map中包含当前字符,表示该字符已经在当前子串中出现过
        if (map.containsKey(s.charAt(right))) {
            // 更新左指针left为当前重复字符的下一个位置(即map.get(s.charAt(right)) + 1)
            left = Math.max(left, map.get(s.charAt(right)) + 1);
        }
        
        // 将当前字符添加到map中,并用右指针right作为值
        map.put(s.charAt(right), right);
        
        // 更新最长不重复子串的长度为右指针right与左指针left之间的距离加1(即当前子串的长度)
        max = Math.max(max, right - left + 1);
    }
    
    // 返回最长不重复子串的长度
    return max;
}

搞定~

1.2 至多包含两个不同字符的最长子串

没有会员,略~~

代码给上,自行理解:

注意!!!md!!没看清题目,原来是找至多包含两个不同字符,是不同字符,不是数量,靠!!

public int lengthOfLongestSubstringTwoDistinct(String s) {
    // 如果字符串长度小于3,直接返回字符串长度
    if (s.length() < 3) {
        return s.length();
    }
    
    // 初始化左指针和右指针为0,创建HashMap来存储字符和其位置的映射关系
    int left = 0, right = 0;
    HashMap<Character, Integer> hashmap = new HashMap<>();
    // 初始化最大长度为2
    int maxLen = 2;
    
    while (right < s.length()) {
        // 如果HashMap的大小小于3,表示当前窗口中的字符种类还没有达到3个
        // 将当前字符及其位置加入HashMap,并将右指针向右移动一位
        if (hashmap.size() < 3) {
            hashmap.put(s.charAt(right), right++);
        }
        
        // 如果HashMap的大小等于3,表示当前窗口中的字符种类已经达到了3个
        if (hashmap.size() == 3) {
            // 找到要删除的字符位置,即HashMap中值最小的位置
            int del_idx = Collections.min(hashmap.values());
            // 删除该位置对应的字符及其位置
            hashmap.remove(s.charAt(del_idx));
            // 更新左指针的位置为要删除的位置的下一个位置
            left = del_idx + 1;
        }
        
        // 更新最大长度为当前窗口的长度和之前的最大长度中的较大值
        maxLen = Math.max(maxLen, right - left);
    }
    
    return maxLen;
}

1.3 至多包含k个不同字符的最长子串

没有会员,略~~

直接把2改成K即可,过程代码不写了

2 长度最小的子数组

leetcode 209. 长度最小的子数组

这题比较经典了,做了好多次了,主要就是多了一个判断,跟前面差不多

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int sum = 0;
        int minLen = Integer.MAX_VALUE;
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            while (sum >= target) {
                minLen = Math.min(minLen, right - left + 1);
                sum -= nums[left];
                left++;
            }
        }
        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }
}

3 盛最多水的容器

leetcode 11. 盛最多水的容器

这题主要理解两个地方:

  1. 面积的计算
  2. 短板和长版与面积的关系

本题看似复杂,但其实简单的很。设两指针 i , j ,指向的水槽板高度分别为 h[i] , h[j] ,此状态下水槽面积为S(i,j) 。由于可容纳水的高度由两板中的 短板 决定,因此可得如下面积公式 : S(i,j)=min(h[i],h[j])×(j−i)

在每个状态下,无论长板或短板向中间收窄一格,都会导致水槽底边宽度−1 变短:

  • 若向内移动短板 ,水槽的短板min(h[i],h[j]) 可能变大,因此下个水槽的面积可能增大 。

  • 若向内移动长板 ,水槽的短板min(h[i],h[j]) 不变或变小,因此下个水槽的面积一定变小 。

因此,只要初始化双指针分列水槽左右两端,循环每轮将短板向内移动一格,并更新面积最大值,直到两指针相遇时跳出;即可获得最大面积。

public int minSubArrayLen(int target, int[] nums) {
    int i = 0, j = height.length - 1, res = 0;
 
    while(i < j) {
		res = height[i] < height[j] ? 
		Math.max(res, (j - i) * height[i++]): 
		Math.max(res, (j - i) * height[j--]); 
	}
	return res;
}

4 寻找子串异位词

4.1 字符串的排列

leetcode 567. 字符串的排列

本题因为字符串s1的异位词长度一定是和s2字符串的长度一样的,所以很自然的想到可以以s1.length()为大小截图一个固定窗口,然后窗口一边向右移动,一边比较就行了。此时可以将窗口内的元素和s1先做一个排序,然后再比较即可,但是这样做的问题是排序代价太高了,我们需要考虑性能更优的方法。

所谓的异位词不过两点:字母类型一样,每个字母出现的个数也是一样的。

  1. 题目说s1和s2都仅限小写字母,因此我们可以创建一个大小为26的数组,每个位置就存储从a到z的个数,

  2. 为了方便操作,索引我们使用index=s1.charAt(i) - ‘a’ 来表示,这是处理字符串的常用技巧。

  3. 此时窗口的right向右移动就是执行:charArray2[s2.charAt(right) - 'a']++;

  4. 而left向右移动就是执行:int left = right - sLen1; charArray2[s2.charAt(left) - 'a']--;

public boolean checkInclusion(String s1, String s2) {
    int sLen1 = s1.length(), sLen2 = s2.length();
    if (sLen1 > sLen2) {
        return false;
    }
    
    // 初始化两个长度为26的整型数组,用于记录每个字符出现的次数
    int[] charArray1 = new int[26];
    int[] charArray2 = new int[26];
    
    // 统计s1和s2前sLen1个字符中每个字符出现的次数
    for (int i = 0; i < sLen1; ++i) {
        // 计算字符在字母表中的位置,并将该位置上的计数加1
        ++charArray1[s1.charAt(i) - 'a'];
        ++charArray2[s2.charAt(i) - 'a'];
    }
    
    // 判断s1是否为s2的子串
    if (Arrays.equals(charArray1, charArray2)) {
        return true;
    }
    
    // 滑动窗口遍历s2,判断是否存在匹配的子串
    for (int right = sLen1; right < sLen2; ++right) {
        // 右移窗口,即将右边界向右移动一个位置
        charArray2[s2.charAt(right) - 'a']++;
        
        // 左移窗口,即将左边界向右移动一个位置
        int left = right - sLen1;
        charArray2[s2.charAt(left) - 'a']--;
        
        // 判断s1是否为当前窗口内的子串
        if (Arrays.equals(charArray1, charArray2)) {
            return true;
        }
    }
    
    return false;
}

4.2 找到字符串中所有字母异位词

leetcode 438. 找到字符串中所有字母异位词

这道题跟上面几乎一模一样,唯一不同的是需要用一个List,如果出现异位词,还要记录其开始位置,那直接将其add到list中就可以了。

public List<Integer> findAnagrams(String s, String p) {
    int sLen = s.length(), pLen = p.length();
    if (sLen < pLen) {
        return new ArrayList<Integer>();
    }
    
    // 初始化两个长度为26的整型数组,用于记录字符串中每个字符出现的次数
    int[] sCount = new int[26];
    int[] pCount = new int[26];
    
    // 统计p和s的前pLen个字符中每个字符出现的次数
    for (int i = 0; i < pLen; i++) {
        // 计算字符在字母表中的位置,并将该位置上的计数加1
        sCount[s.charAt(i) - 'a']++;
        pCount[p.charAt(i) - 'a']++;
    }
    
    // 判断是否存在字母异位词的起始索引
    List<Integer> ans = new ArrayList<Integer>();
    if (Arrays.equals(sCount, pCount)) {
        ans.add(0);
    }
    
    // 滑动窗口遍历s,判断是否存在字母异位词的起始索引
    for (int left = 0; left < sLen - pLen; left++) {
        // 左移窗口,即将左边界向右移动一个位置
        sCount[s.charAt(left) - 'a']--;
        
        // 右移窗口,即将右边界向右移动一个位置
        int right = left + pLen;
        sCount[s.charAt(right) - 'a']++;
        
        // 判断当前窗口内的字符是否与p中字符的出现次数相同
        if (Arrays.equals(sCount, pCount)) {
            // 上面left多减了一次,所以需要加1
            ans.add(left + 1);
        }
    }
    
    return ans;
}

over~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值