日常系列LeetCode《12·滑动窗口篇》

Posted 常某某的好奇心

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了日常系列LeetCode《12·滑动窗口篇》相关的知识,希望对你有一定的参考价值。

数据规模->时间复杂度

<=10^4 😮(n^2)
<=10^7:o(nlogn)
<=10^8:o(n)
10^8<=:o(logn),o(1)

总结


伪代码

#最长窗口
int left = 0, right = 0;
ans=mapset,变量;
while (right < nums.length) 
    处理 right 指向的元素,将其加入到当前窗口

    while (窗口满足条件) 
        // 计算结果
        处理 left 指向的元素,将其从当前窗口中剔除掉
        left++;
    
    // 计算结果
    right++;


#最短窗口
int left = 0, right = 0;
ans=mapset,变量;
while (right < nums.length) 
    处理 right 指向的元素,将其加入到当前窗口

    while (窗口不满足条件) 
        // 计算结果
        处理 left 指向的元素,将其从当前窗口中剔除掉
        left++;
    
    // 计算结果
    right++;

lc 643 :子数组最大平均数 I
https://leetcode.cn/problems/maximum-average-subarray-i/
提示:
n == nums.length
1 <= k <= n <= 10^5
-10^4 <= nums[i] <= 10^4

#方案一:暴力解法存在重复计算
#方案二:前缀和(空间换时间)
class Solution:
    def findMaxAverage(self, nums: List[int], k: int) -> float:
        n=len(nums)
        #o(n)
        prefixsum=[0]*(n+1)
        for i in range(1,n+1):
            prefixsum[i]=prefixsum[i-1]+nums[i-1]
        #o(n)
        maxv=float('-inf')
        for i in range(n-k+1):
            sum1=prefixsum[i+k]-prefixsum[i]
            maxv=max(maxv,sum1)
        #
        return maxv/k 
#方案三:滑动窗口
class Solution:
    def findMaxAverage(self, nums: List[int], k: int) -> float:
        #o(1)
        n=len(nums)
        maxv=float('-inf')
        currwindowsum=0
        #o(2n)
        left=right=0
        while right<n:
            currwindowsum+=nums[right]
            #更新
            if right-left+1==k:
                maxv=max(maxv,currwindowsum)
                currwindowsum-=nums[left]
                left+=1
            right+=1 
        #
        return maxv/k

lc 209【剑指 008】:长度最小的子数组
https://leetcode.cn/problems/minimum-size-subarray-sum/
提示:
1 <= target <= 10^9
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^5
进阶:
如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

#方案一:滑动窗口
class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        minl=inf=float('inf')
        currwindowsum=0
        #
        left=right=0
        while right<len(nums):
            currwindowsum+=nums[right]
            #更新:注意while,非if
            while currwindowsum>=target:
                minl=min(minl,right-left+1)
                currwindowsum-=nums[left]
                left+=1
            right+=1
        #
        return minl if minl!=inf else 0
#方案二:前缀和(注:升序)+二分法
class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        #
        minl=inf=float('inf')
        prefixsum=[0]*(len(nums)+1)
        for i in range(1,len(nums)+1):
            prefixsum[i]=prefixsum[i-1]+nums[i-1]
        #
        for i in range(len(nums)+1):
            t=target+prefixsum[i]
            j=self.findplace(prefixsum,t)
            if j==-1:continue
            minl=min(minl,j-i) #注意: pre[j]-pre[i]=nums.sum[i,j)>=target,个数:j-i
        #
        return minl if minl!=inf else 0
        
    #o(nlogn)
    def findplace(self,nums,target):
        left=0
        right=len(nums)-1
        while left<=right:
            mid=left+(right-left)//2
            if nums[mid]>=target:
                if mid==0 or nums[mid-1]<target:return mid
                else:right=mid-1
            else:left=mid+1
        #
        return -1

lc 3【剑指 016】【top100】:无重复字符的最长子串
https://leetcode.cn/problems/longest-substring-without-repeating-characters/
提示:
0 <= s.length <= 5 * 10^4
s 由英文字母、数字、符号和空格组成

#方案一:滑动窗口+HashSet
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        #
        n=len(s)
        if n<=1:return n
        #o(n),o(2n)
        maxl=1
        left=right=0
        hashset=set()
        while right<n:
            #写法1
            while s[right] in hashset:
                hashset.remove(s[left])
                left+=1
            maxl=max(maxl,right-left+1)
            hashset.add(s[right])
            right+=1
            #写法2
            # if s[right] not in hashset:
            #     maxl=max(maxl,right-left+1)
            #     hashset.add(s[right])
            #     right+=1
            # else:
            #     #key:因为之前s[left]已经被统计过了,所以这里通过去s[left]来缩小窗口
            #     hashset.remove(s[left])
            #     left+=1
        #
        return maxl
        
#方案二:滑动窗口+HashMap
'''
#以上当在窗口中存在重复字符,是一个一个字符的缩小窗口
#我们可以通过记住每个字符在字符串中的索引,当遇到重复字符的时候,就可以直接跳到重复字符的后面
'''
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:     
    	#
        n=len(s)
        if n<=1:return n
        #o(n),o(n)
        maxl=1
        left=right=0
        hashmap= #用于判重
        while right<n:
        	#更新
            #key:有重复,left则跳到重复元素对应索引后一位;无重复,left不变->一直在维护各种无重复子串
            left=max(hashmap.get(s[right],-1)+1,left)
            maxl=max(maxl,right-left+1
            #
            hashmap[s[right]]=right #注:重复元素会再次被更新索引
            right+=1
        #
        return maxl
#方案三:使用数组代替HashMap  
'''
// s 由英文字母、数字、符号和空格组成
'''  
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        #
        n=len(s)
        if n<=1:return n
        #o(1),o(n)
        maxl=1
        left=right=0
        arr=[-1]*128 #ASSIC码0-127:128个字符(英文字母、数字、符号和空格)  #作用:判重
        while right<n:
            #1)key:有重复,left则跳到重复元素对应索引后一位;无重复,left不变
            left=max(arr[ord(s[right])]+1,left)
            #2)非重复式更新
            maxl=max(maxl,right-left+1)
            #
            arr[ord(s[right])]=right #相同元素索引,一直被维护和更新
            right+=1
        #
        return maxl

lc 76【top100】:最小覆盖子串
https://leetcode.cn/problems/minimum-window-substring/
提示:
1 <= s.length, t.length <= 105
s 和 t 由英文字母组成
进阶:
你能设计一个在 o(n) 时间内解决此问题的算法吗?

#方案一:滑动窗口
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        #s 和 t 由英文字母组成
        cnt_t=[0]*60  #'''标记1单元素个数'''
        singlesumT=0  #'''标记2非重复个数'''
        for c in t:
            if cnt_t[ord(c)-ord('A')]==0:
                singlesumT+=1
            cnt_t[ord(c)-ord('A')]+=1
        #o(1),o(2n)
        left=right=0
        cnt_window=[0]*60 #'''标记3单元素个数'''
        singlesumW=0      #'''标记4非重复个数'''
        minlen=[-1,0,0]   #'''标记5最小长度,对应子串的left、right'''
        while right<len(s):
            cnt_window[ord(s[right])-ord('A')]+=1
            if cnt_window[ord(s[right])-ord('A')]==cnt_t[ord(s[right])-ord('A')]:
                singlesumW+=1
            #更新->寻最小
            while singlesumW==singlesumT:
                if minlen[0]==-1 or right-left+1<minlen[0]:
                    minlen[0]=right-left+1
                    minlen[1]=left
                    minlen[2]=right
                cnt_window[ord(s[left])-ord('A')]-=1
                if cnt_window[ord(s[left])-ord('A')]<cnt_t[ord(s[left])-ord('A')]:
                    singlesumW-=1
                left+=1    
            right+=1
        #
        return s[minlen[1]:minlen[2]+1] if minlen[0]!=-1 else ''

lc 485 :最大连续 1 的个数
https://leetcode.cn/problems/max-consecutive-ones/
提示:
1 <= nums.length <= 10^5
nums[i] 不是 0 就是 1.

#方案一:线性遍历
class Solution:
    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
        #o(1),o(n)
        ones=maxl=0
        #写法1
        for n in nums:
            if n==1:
                ones+=1
                maxl=max(maxl,ones)
            else:
                ones=0
        return maxl
        #写法2
        #     if n==1:
        #         ones+=1
        #     else:
        #         maxl=max(maxl,ones) #bug:遇0才更新,如果后面都是零,将得不到更新
        #         ones=0
        # #
        # return max(maxl,ones)    #[1,1,0,1,1,1]
        
#方案二:滑动窗口
class Solution:
    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
        #o(1),o(n)
        maxl=0
        left=right=0
        #写法1
        while right<len(nums):
            if nums[right]==1:
                maxl=max(maxl,right-left+1)
            else:
                left=right+1
            right+=1
        return maxl
        #写法2
        # while right<len(nums):
        #     if nums[right]==0:
        #         maxl=max(maxl,right-left)
        #         left=right+1
        #     right+=1
        # return max(maxl,right-left)

lc 487 :最大连续1的个数 II
https://leetcode.cn/problems/max-consecutive-ones-ii/
提示:
1 <= nums.length <= 10^5
nums[i] 不是 0 就是 1.
进阶:
如果输入的数字是作为 无限流 逐个输入如何处理?换句话说,内存不能存储下所有从流中输入的数字。您可以有效地解决吗?

#方案一:滑动窗口
class Solution:
    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
        maxl=0
        left=right=0
        currwin=0
        while right<len(nums):
            if nums[right]==0:
                currwin+=1
                #更新
                if currwin==2:
                    maxl=max(maxl,right-left)
            #浓缩:这种方法需全放内存
            while currwin==2:
                if nums[left]==0:currwin-=1
                left+=1
            right+=1
        return max(maxl,right-left) 

#方案二:解决无限流->标记位置
class Solution:
    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
        maxl=0
        left=right=0
        zeroIndex=-1 #记录当前窗口0所在位置
        while right<len(nums):
            #更新
            if nums[right]==0:
                if zeroIndex>=0: #说明当前窗口已经有0
                     maxl=max(maxl,right-left)
                     left=zeroIndex+1
                #浓缩
                zeroIndex=right       
            right+=1
        return max(maxl,right-left) 

lc 1004 :最大连续 1 的个数 III
https://leetcode.cn/problems/max-consecutive-ones-iii/
提示:
1 <= nums.length <= 10^5
nums[i] 不是 0 就是 1
0 <= k <= nums.length

#方案一:滑动窗口
class Solution:
    def longestOnes(self, nums: List[int], k: int) -> int:
        maxl=0
        left=right=0
        currzerosum=0
        while right<len(nums):
            if nums[right]==0:
                currzerosum+=1
                if currzerosum==k+1:
                    maxl=max(maxl,right-left)
            #缩
            while currzerosum==k+1:
                if nums[left]==0:currzerosum-=1
                left+=1
            #
            right+=1
        #
        return max(maxl,right-left)

lc 1151 :最少交换次数来组合所有的 1
https://leetcode.cn/problems/minimum-swaps-to-group-all-1s-together/
提示:
1 <= data.length <= 10^5
data[i] == 0 or 1.

'''
‘聚合1’=>问题转变为:满足窗口大小为k时,统计最小为0的个数(即需要交换个数)
'''
#方案一:滑动窗口
class Solution:
    def minSwaps(self, data: List[int]) -> int:
        #统计1个数
        k=0
        k=sum(c for c in data if c==1)
        # for c in data:
        #     if c==1:k+=1 
        #o(1),o(n)
        left=right=0
        currwinzero=0
        minzerosum=inf=float('inf') #bug:若 minzerosum=0,data=[1,0,1,0,1]->minzerosum=max(1,0)=0,错误
        while right<len(data):
            if data[right]==0:
                currwinzero+=1
            #更新
            if right-left+1==k:
                minzerosum=min(minzerosum,currwinzero)
                if data[left]==0:currwinzero-=1
                left+=1
            right+=1
        return minzerosum if minzerosum!=inf else 0

lc 30 :串联所有单词的子串
https://leetcode.cn/problems/substring-with-concatenation-of-all-words/
提示:
1 <= s.length <= 10^4
s 由小写英文字母组成
1 <= words.length <= 5000
1 <= words[i].length <= 30
wor

以上是关于日常系列LeetCode《12·滑动窗口篇》的主要内容,如果未能解决你的问题,请参考以下文章

leetcode之滑动窗口算法小结

leetcode之滑动窗口算法小结

leetcode 1869. 哪种连续子字符串更长---滑动窗口篇3,双指针篇4

leetcode 3. 无重复字符的最长子串----滑动窗口篇1,双指针篇1

leetcode 30. 串联所有单词的子串----滑动窗口篇八

leetcode 1208. 尽可能使字符串相等-----滑动窗口篇五,前缀和篇一,二分篇一