注意题目是说找连续数组的和>=s的最小长度,是“和”,不然都不知道题目在说什么。
http://【拿下滑动窗口! | LeetCode 209 长度最小的子数组】 https://www.bilibili.com/video/BV1tZ4y1q7XE/?share_source=copy_web
看一下暴力算法,暴力算法是用两个for循环,一个循环起始位置,一个循环终止位置。进入第一个循环起始位置固定,然后进入第二个循环终止位置遍历数组去寻找>=s的数组,然后再进入第一个循环起始位置移动,终止位置再遍历数组去寻找此位置为起始位置的和>=s的数组,如此下去。
接下来是滑动窗口,滑动窗口其实和双指针有点相似,滑动窗口方法用一个for循环解决暴力算法中两个for循环。
首先要弄清楚:
①滑动窗口的for循环里for(j..)循环的j指的是起始位置还是终止位置?
假设是起始位置,那为了寻找出和>=s的数组,终止位置就需要遍历数组,那起始位置每移动一个,终止位置就得遍历一遍数组,那就和暴力算法一样了需要两个循环,那就不是滑动窗口了。
所以这里要注意,这个循环的j指的是终止位置。
②那起始位置应该怎么动什么时候应该移动?这个是滑动窗口里很重要的。
for循环,终止位置不断往右移动,当起始位置和终止位置之间的数组和>=s,起始位置就得往右移动了,为什么?
因为如果起始位置还不动,而终止位置继续往右移动,因为此时这道题数组里是正数,所以长度肯定越来越大,可是我们要找的是最短长度啊,所以这样是无意义的嘛。所以此时应该是终止位置暂时别动,起始位置往后移动,因为此时有可能出现这种情况:s=100,然后你此时窗口是[4,2,98],这里面还有更短的[2,98],所以此时起始位置应该往后移动去寻找这个窗口里的更短数组。
③有个小细节要注意,让起始位置往右移的时候是用if还是while呢?
因为有可能出现这种情况,你当前窗口为[2,2,2,2,2,98],如果是if,那你起始位置移动到第二个2就停下来了,然后接着跳出循环继续终止位置右移的循环,可是你起始位置只移动一位[2,2,2,2,98]这个并不是我们要的最短长度啊,所以应该是用while,让起始位置不断往后移直至找到最短长度。
④那找出各位置的最短数组后,有这么多数组,哪个才是最短的?
就像伪代码里的,你定义一个东西记录当前的最短长度,然后每次找到一个最短长度时就和那个最短长度比较,用min()留下最短的长度嘛。
滑动窗口的套路一般是这样的:
i=0;
for(j=0;j<=nums.size){
sum+=nums[j]; //sum是记录窗口里的和
while(sum>=s){
sub=j-i-1; //sub是记录当前窗口长度
result=min(result,sub); //就选出最短的那个长度
sum=sum-nums[i]; //起始位置移动了窗口的和就减少了嘛
i++;
}
return result;
}
这里的时间复杂度是O(n)而不是O(n^2),不要以为for里面放一个while就以为是O(n^2),主要是看每个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是2*n也就是O(n),空间复杂度是O(1)
练习题:
①leetcode 209.长度最小的子数组(就是以这道题为例的)
②leetcode 904.水果成篮
这道题和上一道题有什么区别?区别在于上面那道题求的是最小滑动窗口,而本题求的是最大滑动窗口。最小滑动窗口和最大滑动窗口有什么不一样吗?
关键的区别在于,最小滑动窗口是在左边界右移的过程中更新结果(因为要找最短的情况嘛),是在左边界右移的循环内更新结果,为什么?因为你求的是最短长度嘛,左边界右移就在不断变短呀所以就要不断更新你的结果呀; 最大滑动窗口是在右边界右移的过程中更新结果(因为要找最长的情况嘛),是在右边界右移的循环内(左边界右移循环外)更新结果,为什么?因为你求的是最长长度嘛,右边界右移就在不断变长呀所以就要不断更新你的结果呀。
上道题是求和,所以定义个sum加和即可;这道题是限定只能有两种果类,所以可以考虑用哈希表,哈希表记录目前窗口出现的数及其出现的次数,也可以直接定义一个数组cnts来记录当前窗口出现的数及其出现的次数。
思路大致就是,定义left,right,做循环右指针往右移,统计每种数及其出现的次数,当出现哈希表.size()>2即需要进入循环让左指针往右移动,直至把其中某个数的出现次数变为0,即删除它,最终使得里面的数只有两种或者两种以下。(目前对哈希表都陌生了)
③leetcode 76.最小覆盖子串
这道题不是很透
http://【【LeetCode 每日一题】76. 最小覆盖子串 | 手写图解版思路 + 代码讲解-哔哩哔哩】 https://b23.tv/isdKQKf
这位答主的挺好,只是我还是有点不明白如果有D、E这种字母那咋弄的?此时hs[D]=1,可是ht[D]不存在呀,不存在就直接不理了吗因为没有满足if(hs[s[i]]<=ht[s[i]]),所以直接加入窗口就不用管了。反正长度可以直接i-j+1。
答主思路应该是这样: 定义两个哈希表,一个记录字符串t出现的字母以及其出现次数即ht,一个当前滑动窗口出现的字母及其出现次数即hs。然后定义一个cnt记录有效字符,注意什么是有效字符,s的下标为i的字符加入到hs中后,hs[s[i]]<=ht[s[i]] 才算有效字符,如ht里1个A,当前滑动窗口里就一个A那这个A就是有效字符,比如滑动窗口为[BAADC]而ht[]里就要一个A,所以B和第二个A就不是有效值了,到第一个A的时候就满足cnt=t.length了,左边界就可以右移找出当前窗口的最短长度了。
l=0,r=0,每次循环都有三步动作:
第一步是把当前数值加入到窗口中即hs[s[r]]++,如果加入后没有超过ht中该数值的数量即hs[s[r]]<=ht[s[r]],则说明该字符是一个有效字符,我们就要把cnt值+1,
第二步是因为我们已经在窗口中加入了一个新的字符,所以左侧可能存在冗余字符要删去,当hs[s[l]]>ht[s[l]],意思就是多余了,即做循环左边界往右移,如[ABAC]找AC,到ABA时,因为只要一个A,所以到第二个A时,左边界就往右移动,而B也是冗余的,所以再继续移动,所以是用循环while而不是if.
第三步是窗口已经移动完整,如果cnt值等于字符串t的长度,就说明窗口中已经覆盖了t中所有的字符,只要根据窗口长度(i-j+1)更新结果字符串就可以了。