Leetcode-滑动窗口

滑动窗口模板及例题

滑动窗口为了解决时间复杂度而提出的一种优化方法,一般可以在O(n)的时间复杂度下解决O(n2)的问题,因此这里进行提供模板,以及Leetcode例题方便读者进行理解。

例题理解

下面我们首先给出一道Leetcode中的例题来进行理解,并给出解题模板。
Leetcode 最长连续子串

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

输入: s = “abcabcbb”
输出: 2
解释: 因为最长子重复串是 “bb”

输入: s = “bbbbb”
输出: 5
解释: 因为无重复字符的最长子串是 “bbbbb”

通过上面的例子我们已经可以题意要求了,就是给定字符串,求解最长的无重复字串,这里需要注意子串和子序列的区别。本题通过简单的模拟方法当然可以解决,但是我们希望通过这道题引出滑动窗口的方法。
滑动窗口,重点在几个变量,分别是 left、right、map 后面将分别解释这三个变量。

left:左边界变量。
right:右边界变量。
map:从左边界到右边界的中间变量的存储。

通过 map 变量其实可以看到,滑动窗口更是一种Hash Table的应用。本道题我们在每一移动右边界的过程中,都会接着进行判断,左边界的位置是否合适,这个判断方法也就是通过map来进行判断,本题map变量存储的就是从左边界到右边界出现过的 字符 的出现次数,并根据此来判断从左边界到右边界是否为最长的连续子串。下面查看代码。

public int getMaxLen(String s){
	int left = 0;
	int right = 0;
	int result = Integer.MIN_VALUE;
	HashMap<Character,Integer>map = new HashMap<>();
	while(right<s.length()){
//		下面是移动右边界
		map.put(s.charAt(right),map.getOrDefault(s.charAt(right),0)+1);
		right++;
//		下面是在右边界移动完成之后,移动左边界
		while(map.keySet().size()!=1){
			if(map.get(s.charAt(left))==1){
				map.remove(s.charAt(left));
			}else{
				map.put(s.charAt(left),map.get(s.charAt(left))-1);
			}
			left++;
		}		
//		下面是更新最长的子串
		result = Math.max(result,right-left);
	}
	return result;

}

通过上面的代码可以看到,我们其实是在每一次移动右边界之后,会相应的更新左边界,更新的过程正是通过map进行的,其实还可以通过ArrayList等变量进行更新,这就需要具体判断,在更新完成之后,就可以更新最后结果。上面是个人手撸代码,大家可以根据题意进行改变代码解题。

相关例题

下面提供一些例题以及相关题解,帮助大家理解。

Leetcode 1208.尽可能使字符串相等

给你两个长度相同的字符串,st

s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。

用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。

如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。

如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。

输入:s = “abcd”, t = “bcdf”, cost = 3
输出:3
解释:s 中的 “abc” 可以变为 “bcd”。开销为 3,所以最大长度为 3。

输入:s = “abcd”, t = “cdef”, cost = 3
输出:1
解释:s 中的任一字符要想变成 t 中对应的字符,其开销都是 2。因此,最大长度为 1。

读完题之后,其实就是在我们前面的模板之上多了一点步骤,就是手动求出花费数组,然后求解花费数组的最长子数组,并且最长子数组求和小遇maxCost。下面看下我们的题解。

public int equalSubstring(String s, String t, int maxCost) {
	if(s.length()!=t.length()){
		return 0;
	}
	int []diff = new int[s.length()];
	for(int i=0;i<s.length();i++){
		diff[i] = Math.abs(s.charAt(i)-t.charAt(i));
	}
	int left = 0;
	int right = 0;
	int maxLen = 0;
	int tempCost = 0;
	while(right<diff.length){
		tempCost+=diff[right];
		right++;
		while(tempCost>maxCost){
			tempCost-=diff[left];
			left++;
		}
		maxLen = Math.max(maxLen,right-left);
	}
	return maxLen;
}
Leetcode 3.无重复字符的最长字串

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

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

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

首先利用前面的模板,给大家看下如何灵活运用。

public static int lengthOfLongestSubstring(String s) {
    int left = 0;
    int right = 0;
    int max = 0;
    HashMap<Character,Integer>map = new HashMap<>();
    ArrayList<Character>arr = new ArrayList<>();
    while(right<s.length()){
        map.put(s.charAt(right),map.getOrDefault(s.charAt(right),0)+1);
        arr.add(s.charAt(right));
        right++;
        if(map.keySet().size()!=arr.size()){
            Character remove = arr.remove(0);
            if(map.get(remove)==1){
                map.remove(remove);
            }else{
                map.put(remove,map.get(remove)-1);
            }
            left++;
        }
        max = Math.max(max,right-left);
    }
    return max;
}

从上面代码中可以看到,我们除了HashMap,同样运用了ArrayList来进行左边界的移动,这里进行判重的原则使 map.keySet().size()!=arr.size(),这样子就可以控制左边界的移动,滑动窗口是一种思想,我们只能够根据题意去适当的套用模板,而不能够完全照搬。

下面是一种更加简便的思想,将代码贴出。

public static int lengthOfLongestSubstring(String s) {
	int left = 0;
	int right = 0;
	int max = 0;
//	这里map用来保存字符上一次出现的index
	HashMap<Character,Integer>map = new HashMap<>();
	while(right<s.length()){
		if(map.containsKey(s.charAt(right))){
//			这里的左边界移动要好好理解。
			left = Math.max(left,map.get(s.charAt(right)));
		}
		right++;
		max = Math.max(max,right-left);
	}
	return max;
}
Leetcode 1934 最短超串

假设你有两个数组,一个长一个短,短的元素均不相同。找到长数组中包含短数组所有的元素的最短子数组,其出现顺序无关紧要。
返回最短子数组的左端点和右端点,如有多个满足条件的子数组,返回左端点最小的一个。若不存在,返回空数组。

输入:
big = [7,5,9,0,2,1,3,5,7,9,1,1,5,8,8,9,7]
small = [1,5,9]
输出: [7,10]

输入:
big = [1,2,3]
small = [4]
输出: []

这里主要注意左边界是如何移动的。下面看下代码。

public static int[] shortestSeq(int[] big, int[] small) {
	int minLen = Integer.MAX_VALUE;
	int left = 0;
	int right = 0;
	HashSet<Integer>set = new HashSet<>();
	for(int temp:small){
		set.add(temp);
	}
	boolean flag = false;
	int []result = new int[2];
	HashMap<Integer,Integer>map = new HashMap<>();
	while(right<big.length){
		map.put(big[right],map.getOrDefault(big[right],0)+1);
		right++;
		while((left<right)&&((!set.contains(big[left]))||(map.get(big[left])>1))){
			map.put(big[left],map.get(big[left])-1);
			left++;
		}
		if(map.keySet().containsAll(set)){
			flag = true;
			if(right-left<min){
				min = right-left;
				a[0] = left;
				a[1] = right-1;
			}
		}
	}
	if(flag){
		return result;
	}
	reutrn new int[0];
}

处女作,希望能够给大家提供一些帮助,个人认为这里主要还是需要理解左边界的移动过程。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值