【日常系列】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
words[i] 由小写英文字母组成
在这里插入图片描述

#方案一:哈希查找
class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        singleL=len(words[0])
        totalL=len(words)*singleL
        #
        hashset1={word:0 for word in words}
        for w in words:
            hashset1[w]+=1
        #0(2n),o(mn)
        res=[]    
        for i in range(len(s)-totalL+1):
            #子串
            onews=s[i:i+totalL]
            hashset2={} #注意位置<-新子串
            for j in range(0,totalL,singleL):
                #
                onew=onews[j:j+singleL]
                if onew in hashset2:
                    hashset2[onew]+=1
                else:
                    hashset2[onew]=1
            if hashset2==hashset1:
                res.append(i)
        return res
        
#方案二:滑动窗口
class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        singleL=len(words[0])
        totalL=len(words)*singleL
        #
        hashset1={word:0 for word in words}
        for w in words:
            hashset1[w]+=1
        #o(n),o(30n) #1<=words[i].length<=30
        res=[]
        for i in range(singleL):#bug:left+=singleL带来的
            left=right=i
            hashset2={}
            matchedN=0 #位置:每次的初始化
            while right<len(s)-singleL+1:
                #扩
                nextw=s[right:right+singleL]
                if nextw in hashset2:
                    hashset2[nextw]+=1
                else:hashset2[nextw]=1
                matchedN+=1 #假定
                #缩    
                while hashset2.get(nextw,0)>hashset1.get(nextw,0): #key:while-一直对应单词重复太多/生单词
                    matchedN-=1
                    leftw=s[left:left+singleL]
                    hashset2[leftw]-=1
                    left+=singleL #key:left+=1   ->[footheyyyfoo]未直接跳过yyy 
                if matchedN==len(words):
                    res.append(left)
                right+=singleL #key:right+=1     ->[foothe]!=[foo]+[oot]   
        return res

lc 567【剑指 014】:字符串的排列
https://leetcode.cn/problems/permutation-in-string/
提示:
1 <= s1.length, s2.length <= 10^4
s1 和 s2 仅包含小写字母
在这里插入图片描述

#方案一:计数
class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        #s1 和 s2 仅包含小写字母
        n,m=len(s1),len(s2)
        if n>m:return False
        #o(1),o(n):前n/(i-n->n)比较
        cnt1=[0]*26
        cnt2=[0]*26
        for i in range(n):
            cnt1[ord(s1[i])-ord('a')]+=1
            cnt2[ord(s2[i])-ord('a')]+=1  
        if cnt1==cnt2:return True
        for j in range(n,m):
            cnt2[ord(s2[j])-ord('a')]+=1
            cnt2[ord(s2[j-n])-ord('a')]-=1
            if cnt1==cnt2:return True
        return False
#方案二:滑动窗口
class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        n,m=len(s1),len(s2)
        if n>m:return False
        cnt=[0]*26
        for i in range(n):
            cnt[ord(s1[i])-ord('a')]+=1
        #o(1),0(n)
        left=right=0
        while right<m:
            cnt[ord(s2[right])-ord('a')]-=1
            while cnt[ord(s2[right])-ord('a')]==-1:
                cnt[ord(s2[left])-ord('a')]+=1
                left+=1
            if right-left+1==n:return True
            right+=1
        #
        return False

lc 763 :划分字母区间
https://leetcode.cn/problems/partition-labels/
提示:
S的长度在[1, 500]之间。
S只包含小写字母 ‘a’ 到 ‘z’ 。

#方案一:滑动窗口
class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        #o(1)
        c2index=[0]*26
        for i in range(len(s)):
            c2index[ord(s[i])-ord('a')]=i
        #o(n)
        left=right=0
        i=0 
        res=[]
        while i<len(s):
            right=max(right,c2index[ord(s[i])-ord('a')])
            if i==right:
                res.append(right-left+1)
                left=right+1
            i+=1
        #
        return res
#方案二:

lc 845 :数组中的最长山脉
https://leetcode.cn/problems/longest-mountain-in-array/
提示:
1 <= arr.length <= 10^4
0 <= arr[i] <= 10^4
进阶:
你可以仅用一趟扫描解决此问题吗?
你可以用 O(1) 空间解决此问题吗?

#方案一:滑动窗口
class Solution:
    def longestMountain(self, arr: List[int]) -> int:
        n=len(arr)
        maxl=0
        left=0
        #o(1),o(n)
        while left+2<n:#至少三元素
            right=left+1
            if right+1<n and arr[left]<arr[right]:
                #高
                while right+1<n and arr[right]<arr[right+1]:
                    right+=1
                if right+1<n and arr[right]>arr[right+1]:
                    #低
                    while right+1<n and arr[right]>arr[right+1]:
                        right+=1
                    maxl=max(maxl,right-left+1)
                else:right+=1 #
            left=right
        return maxl
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值