代码随想录--数组--滑动窗口解决最长/短子数组题型

注意题目是说找连续数组的和>=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;

}

代码随想录--数组--滑动窗口解决最长/短子数组题型_第1张图片

这里的时间复杂度是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,即删除它,最终使得里面的数只有两种或者两种以下。(目前对哈希表都陌生了)

代码随想录--数组--滑动窗口解决最长/短子数组题型_第2张图片

③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)更新结果字符串就可以了。

代码随想录--数组--滑动窗口解决最长/短子数组题型_第3张图片

你可能感兴趣的:(数据结构与算法,算法,数据结构,c++)