跟孙哥学java
滑动窗口的思想
滑动窗口的思想非常简单,如下图所示,假如窗口的大小是3,当不断有新数据来时,我们会维护一个大小
为3的一个区间,超过3的就将新的放入老的移走。
这个过程有点像火车在铁轨上跑,原始数据可能保存在一个很大的空间里(铁轨),但是我们标记的小区间就
像一列长度固定的火车,一直向前走。
有了区间,那我们就可以造题了,例如让你找序列上三个连续数字的最大和是多少,或者子数组平均数是
多少(LeetCode643)等等。
从上面的图可以看到,所谓窗口就是建立两个索引,left和right,并且保持left,right)之间一共有3个元
素,然后一边遍历序列,一边寻找,每改变一次就标记一下当前区间的最大值就行了。
这个例子已经告诉我们了什么是窗口、什么是窗口的滑动:
窗口:窗口其实就是两个变量lft和right之间的元素,也可以理解为一个区间。窗口大小可能固定,也 可能变化,如果是固定大小的,那么自然要先确定窗口是否越界,再执行逻辑处理。如果不是固定的, 就要先判断是否满足要求,再执行逻辑处理。 滑动:说明这个窗口是移动的,事实上移动的仍然是Ift和righti两个变量,而不是序列中的元素。当变 量移动的时,其中间的元素必然会发生变化,因此就有了这种不断滑动的效果。 在实际问题中,窗口大小不一定是固定的,我们可以思考两种场景: 1.固定窗口的滑动就是火车行驶这种大小不变的移动。 2.可变的窗口就像两个老师带着一队学生外出,一个负责开路,一个负责断后,中间则是小朋友。两位老 师之间的距离可能有时大有时小,但是整体窗口是不断滑动的。 根据窗口大小是否固定,可以造出两种类型的题: 1.如果是固定的,则一般会让你求哪个窗口的元素最大、最小、平均值、和最大、和最小等等类型的问 题。 2.如果窗口是变的,则一般会让你求一个序列里最大、最小窗口是什么等等。 滑动窗口题目本身没有太高的思维含量,但是实际在解题的时候仍然会感觉比较吃力,主要原因有以下几 点: 1.解题最终要落实到数组上,特别是边界处理上,这是容易晕的地方,稍有疏忽就难以得到准确的结果。 2.有些元素的比较、判断等比较麻烦,不仅要借助集合等工具,而且处理过程中还有一些技巧,如果不熟 悉会导致解题难度非常大。 3.堆!我们在前面介绍过,堆结构非常适合在流数据中找固定区间内的最大、最小等问题。因此比滑动窗口 经常和堆一起使用可以完美解决很多复杂的问题。 最后一个问题,那双指针和滑动窗口啥区别呢?根据性质我们可以看到,滑动窗口是双指针的一种类型, 主要关注两个指针之间元素的情况,因此范围更小一些,而双指针的应用范围更大,花样也更多。
两个入门题
子数组最大平均数
子数组最大平均数
LeetCode643给定n个整数,找出平均数最大且长度为k的连续子数组,并输出该最大平均数。
class Solution {
public double findMaxAverage(int[] nums, int k) {
int sum=0;
if(nums.length<k) return -1;
for (int i = 0; i < k; i++) {
sum+=nums[i];
}
int start=0;
int max=sum;
for (int i = k; i < nums.length; i++) {
sum=sum-nums[start++]+nums[i];
max=Math.max(max,sum);
}
return max*1.0/k;
}
}
最长连续增长序列
我们再看一个窗户变化的情况。LeetCode674.给定一个未经排序的整数数组,找到最长且连续递增的子序 列,并返回该序列的长度。 连续增长序列 连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。
class Solution {
public int findLengthOfLCIS(int[] nums) {
if(nums==null||nums.length==0) return 0;
int max=1;
int l=0;
int r=1;
int len=nums.length;
while (r<len){
if(nums[r]-nums[r-1]>0){
//递增
r++;
if(r==len){
max=Math.max(max,r-l);
}
continue;
}else {
//不递增了
max=Math.max(max,r-l);
l=r++;
}
}
return max;
}
}
最长字串专题
先来看一道高频算法题:无重复字符的最长子串。具体要求是给定一个字符串S,请你找出其中不含有重 复字符的最长子串的长度。例如,输入:s="abcabcbb"则输出3,因为无重复字符的最长子串是"abc", 所以其长度为3。 怎么做后面再说,如果再变一下要求,至多包含两个不同字符的最长子串,该怎么做呢? 再变一下要求,至多包含K个不同字符的最长子串,该怎么做呢? 到这里是否感觉,这不在造题吗?是的!上面就分别是LeetCode3、159、340题,而且这几道题都可以用 滑动窗口来解决。学会之后,我们就总结出滑动窗口的解题模板了。 接下来,我们就一道一道看。
无重复字符的最长子串
LeetCode3给定一个字符串s,请你找出其中不含有重复字符的最长子串的长度。例如:
无重复字符的最长子串
class Solution {
public static void main(String[] args) {
String s="abcabcbb";
int i = lengthOfLongestSubstring(s);
}
public static int lengthOfLongestSubstring(String s) {
if(s.length()==0) return 0;
char[] chars = s.toCharArray();
int l=0;
int max=1;
List<Character> list=new ArrayList<>();
list.add(chars[l]);
for (int r = 1; r < chars.length; r++) {
if(list.contains(chars[r])){
//重复了
max=Math.max(max,r-l);
while (list.contains(chars[r])&&l<r){
list.remove(0);
l++;
}
}
list.add(chars[r]);
if(r==chars.length-1){
max=Math.max(max,r-l+1);
}
}
return max ;
}
}
至多包含两个不同字符的最长子串
给定一个字符串s,找出至多包含两个不同字符的最长子串t,并返回该子串的长度,这就是
LeetCode159题。例如:
class Solution {
public static void main(String[] args) {
String s="eceba";
int i = lengthOfLongestSubstringTwoDistinct(s);
System.out.println(i);
}
public static int lengthOfLongestSubstringTwoDistinct(String s) {
Map<Character,Integer> map=new HashMap<>();
if(s.length()<3){
return s.length();
}
int len=s.length();
int l=0;
int r=0;
int max=0;
while (r<len){
char c = s.charAt(r);
map.put(c,map.getOrDefault(c,0)+1);
if(map.size()>2){
//超过两个字符了
max=Math.max(max,r-l);
while (map.size()>2){
map.put(s.charAt(l),map.getOrDefault(s.charAt(l),0)-1);
if(map.get(s.charAt(l))==0){
map.remove(s.charAt(l));
}
l++;
}
}
r++;
if(r==len){
max=Math.max(max,r-l);
}
}
return max;
}
}
至多包含K个不同字符的最长子串
把上面的2改成K既可以
class Solution {
public static void main(String[] args) {
String s="eceba";
int i = lengthOfLongestSubstringTwoDistinct(s,int 2);
System.out.println(i);
}
public static int lengthOfLongestSubstringTwoDistinct(String s,int k) {
Map<Character,Integer> map=new HashMap<>();
if(s.length()<3){
return s.length();
}
int len=s.length();
int l=0;
int r=0;
int max=0;
while (r<len){
char c = s.charAt(r);
map.put(c,map.getOrDefault(c,0)+1);
if(map.size()>k){
//超过两个字符了
max=Math.max(max,r-l);
while (map.size()>2){
map.put(s.charAt(l),map.getOrDefault(s.charAt(l),0)-1);
if(map.get(s.charAt(l))==0){
map.remove(s.charAt(l));
}
l++;
}
}
r++;
if(r==len){
max=Math.max(max,r-l);
}
}
return max;
}
}
长度最小的子数组
LeetCode209.长度最小的子数组,给定一个含有n个正整数的数组和一个正整数target。
找出该数组中满足其和≥target的长度最小的连续子数组[numsl,numsl+-1,,numsr-1,numsr],并
返回其长度。如果不存在符合条件的子数组,返回0。
长度最小的子数组
class Solution {
public static void main(String[] args) {
int[] nums={1,2,3,4,5};
System.out.println(minSubArrayLen(11, nums));
}
public static int minSubArrayLen(int target, int[] nums) {
int l=0;
int min=nums.length+1;
int r=0;
int len=min-1;
int sum=0;
while (r<len){
sum+=nums[r++];
while (sum>=target){
min=Math.min(min,r-l);
sum-=nums[l++];
}
}
return min!=nums.length+1?min:0;
}
}