滑动窗口

本质:可用于解决一些子串,子数组的问题,r指针一直往右走,l指针按条件往右走

76. 最小覆盖子串

3. 无重复字符的最长子串

1004. 最大连续1的个数 III

1052. 爱生气的书店老板

面试题59 - II. 队列的最大值

1438. 绝对差不超过限制的最长连续子数组

1423. 可获得的最大点数

剑指 Offer 48. 最长不含重复字符的子字符串

830. 较大分组的位置

面试题 17.18. 最短超串

76. 最小覆盖子串

1477. 找两个和为目标值且不重叠的子数组

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:

如果 S 中不存这样的子串,则返回空字符串 ""。
如果 S 中存在这样的子串,我们保证它是唯一的答案。

1.构建一个滑动窗口,左右指针怎么移动?右指针移动:当窗口内的内容不满足条件 左指针:当窗口内的指针满足条件

class Solution:
    def minWindow(self, s: 'str', t: 'str') -> 'str':
        t=collections.Counter(t)
        lookup=collections.defaultdict(int)
        minLen=float('inf')
        res=""
        l=r=0
        while r<len(s):
            lookup[s[r]]+=1
            r+=1
            while all(map(lambda x:lookup[x]>=t[x],t.keys())):
                if r-l<minLen:
                    res=s[l:r]
                    minLen=r-l
                lookup[s[l]]-=1
                l+=1
        return res

class Solution:
    def minWindow(self, s: 'str', t: 'str') -> 'str':
        lookup=collections.defaultdict(int)
        for i in t:lookup[i]+=1
        count=len(t)
        l=r=0
        min_len=float('inf')
        res=""
        while r<len(s):
            if lookup[s[r]]>0:count-=1
            lookup[s[r]]-=1
            r+=1
            while count==0:
                if min_len>r-l:
                    min_len=r-l
                    res=s[l:r]
                if lookup[s[l]]==0:count+=1
                lookup[s[l]]+=1
                l+=1
        return res




3. 无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

1.滑动+hash,hash用于存储内容,用于判断当前窗口内是否出现了重复

class Solution:
    def lengthOfLongestSubstring(self, s):
        lookup=collections.defaultdict(int)
        res=0
        l=r=0
        count=0
        while r<len(s):
            if lookup[s[r]]>0:count+=1#这个字符前面出现过
            lookup[s[r]]+=1
            r+=1
            while count>0:
                if lookup[s[l]]>1:count-=1
                lookup[s[l]]-=1
                l+=1
            res=max(res,r-l)
        return res


class Solution(object):
    def lengthOfLongestSubstring(self, s):
        if not s:return 0
        l=r=0
        res=0
        h={}
        while r<len(s):
            if s[r] in h:
                l=max(l,h[s[r]])
            res=max(res,r-l+1)
            h[s[r]]=r+1
            r+=1
        return res

1004. 最大连续1的个数 III

给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。

返回仅包含 1 的最长(连续)子数组的长度。

 

示例 1:

输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释: 
[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。

1.直接暴力o(n³)

2.sum优化成前缀和暴力o(n²)

3.滑动窗口,右指针一直右走,左指针当窗口内的0的个数>k时右走

class Solution:
    def longestOnes(self, A: List[int], K: int) -> int:
        if K>=A.count(0):return len(A)
        res=0
        for i in range(len(A)):
            for j in range(i+1,len(A)):
                if j-i+1-sum(A[i:j+1])==K:
                    res=max(res,sum(A[i:j+1])+K)
        return res

class Solution:
    def longestOnes(self, A: List[int], K: int) -> int:
        if K>=A.count(0):return len(A)
        res=0
        pre=[0 for _ in range(len(A))]
        pre[0]=A[0]
        for i in range(1,len(A)):
            pre[i]=pre[i-1]+A[i]
        for i in range(len(A)):
            for j in range(i+1,len(A)):
                if i-1>=0:sum_=pre[j]-pre[i-1]
                else:sum_=pre[j]
                if j-i+1-sum_==K:
                    res=max(res,sum_+K)
        return res

class Solution:
    def longestOnes(self, A: List[int], K: int) -> int:
        if K>=len(A):return len(A)
        l=r=0
        res=0
        count=0
        while r<len(A):
            if A[r]==0:count+=1
            while count>K:
                if A[l]==0:count-=1
                l+=1
            res=max(res,r-l+1)
            r+=1
        return res

1052. 爱生气的书店老板

今天,书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。

在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。

书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。

请你返回这一天营业下来,最多有多少客户能够感到满意的数量。

1.暴力直接枚举grumpy数组连续的x个数为0的情况

2.滑动窗口

class Solution:
    def maxSatisfied(self, customers: List[int], grumpy: List[int], X: int) -> int:
        res=0
        def func(index):
            ans=0
            for i in range(len(customers)):
                if grumpy[i]==0 or index<=i<=index+X-1:
                    ans+=customers[i]
            return ans

        for i in range(len(grumpy)-X+1):
            res=max(res,func(i))
        return res

class Solution:
    def maxSatisfied(self, customers: List[int], grumpy: List[int], X: int) -> int:
        res1=0
        for i in range(len(customers)):
            if grumpy[i]==0:
                res1+=customers[i]
                customers[i]=0
        l=0
        r=X-1
        tmp_sum=sum(customers[:X])
        res2=tmp_sum
        while r<len(customers):
            r+=1
            if r>len(customers)-1:break
            tmp_sum+=customers[r]
            tmp_sum-=customers[l]
            l+=1
            res2=max(res2,tmp_sum)
            
        return res1+res2


面试题59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]

1.用普通队列实现,max_value用暴力获取其值

2.用一个辅助的单调队列,单调队列的队首始终保持着队列的最大值

class MaxQueue:

    def __init__(self):
        self.q=collections.deque()


    def max_value(self) -> int:
        return max(self.q) if self.q else -1
        


    def push_back(self, value: int) -> None:
        self.q.append(value)


    def pop_front(self) -> int:
        return self.q.popleft() if self.q else -1

class MaxQueue(object):

    def __init__(self):
        self.q=collections.deque()
        self.q_keep=collections.deque()


    def max_value(self):
        return self.q_keep[0] if self.q_keep else -1
  

    def push_back(self, value):
        self.q.append(value)
        while self.q_keep and self.q_keep[-1]<value:
            self.q_keep.pop()
        self.q_keep.append(value)
 
        
    def pop_front(self):
        if not self.q:return -1
        res=self.q.popleft()
        if res==self.q_keep[0]:self.q_keep.popleft()
        return res


1438. 绝对差不超过限制的最长连续子数组

给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。

如果不存在满足条件的子数组,则返回 0 。

1.很好的题,滑动窗口(一般求子串,子区间问题可以考虑),滑动窗口问题需要考虑2个指针怎么移动,r指针可以走的话一直往右走,左指针当窗口内容不满足条件时往左走,这里的条件就是窗口内的max_value-min_value<=limit,这里就涉及到另一个知识点,怎么用o(1)的时间获得当前区间的最值?单调队列!,维护2个单调队列!

class Solution:
    def longestSubarray(self, nums: List[int], limit: int) -> int:
        q_max=[]
        q_min=[]
        res=0
        l=r=0
        while r<len(nums):
            while q_max and nums[r]>nums[q_max[-1]]:
                q_max.pop()
            q_max.append(r)
            while q_min and nums[r]<nums[q_min[-1]]:
                q_min.pop()
            q_min.append(r)
            while q_min and q_max and nums[q_max[0]]-nums[q_min[0]]>limit:
                if q_max[0]<=l:q_max.pop(0)
                if q_min[0]<=l:q_min.pop(0)
                l+=1
            res=max(res,r-l+1)
            r+=1
        return res

1423. 可获得的最大点数

几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。

每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。

你的点数就是你拿到手中的所有卡牌的点数之和。

给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。

输入:cardPoints = [1,2,3,4,5,6,1], k = 3
输出:12
解释:第一次行动,不管拿哪张牌,你的点数总是 1 。但是,先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌,最终点数为 1 + 6 + 5 = 12 。

1.贪心+前缀和

思考?

要怎么才能判断拿左边还是右边呢,想一下,只能拿3张,怎么判断先拿左还是右,不能只看一个数,要看当前可以拿的牌数来决定,对于3张而言就是要一次考虑左右拿3张才能决定是先拿左还是右!

2.dfs直接左右2种情况搜索,超时

3.滑动窗口

分析:

看最后的效果,起始就是一直在左右拿牌,最后剩下中间的连续子数组,所以求中间子数组的最小和即可!

class Solution:
    def maxScore(self, cardPoints: List[int], k: int) -> int:
        n=len(cardPoints)
        left=[0 for _ in range(n+1)]
        right=[0 for _ in range(n+1)]
        for i in range(n):
            left[i+1]=left[i]+cardPoints[i]
            right[i+1]=right[i]+cardPoints[n-i-1]
        res=0
        l=r=0
        u=k
        for i in range(k):
            if left[l+u]-left[l]>right[r+u]-right[r]:
                res+=cardPoints[l]
                l+=1
            else:
                res+=cardPoints[n-r-1]
                r+=1
            u-=1
        return res
        
class Solution:
    def maxScore(self, cardPoints: List[int], k: int) -> int:
        if k==len(cardPoints):return sum(cardPoints)
        def dfs(l,r,k):
            if k==1:return max(cardPoints[l],cardPoints[r])
            return max(dfs(l+1,r,k-1)+cardPoints[l],dfs(l,r-1,k-1)+cardPoints[r])
        return dfs(0,len(cardPoints)-1,k)

class Solution:
    def maxScore(self, cardPoints: List[int], k: int) -> int:
        if len(cardPoints) == k:  
            return sum(cardPoints)
        res_opposite = sum(cardPoints[:len(cardPoints) - k])  
        # 变量存储滑窗的最小值,这里先按初始位置赋初值

        current_sum = res_opposite  # current_sum记录当前滑窗总和
        left_idx, right_idx = 0, len(cardPoints) - k - 1  
        for _ in range(k):  # 滑窗将移动 k 次
            left_idx += 1 
            right_idx += 1
            current_sum = current_sum - cardPoints[left_idx - 1] + cardPoints[right_idx]  
            res_opposite = min(res_opposite, current_sum)  
        return sum(cardPoints) - res_opposite

1477.给你一个整数数组 arr 和一个整数值 target 。

请你在 arr 中找 两个互不重叠的子数组 且它们的和都等于 target 。可能会有多种方案,请你返回满足要求的两个子数组长度和的 最小值 。

请返回满足要求的最小长度和,如果无法找到这样的两个子数组,请返回 -1 。

1.滑窗生成所有结果,根据长度排序,最后选出不相交

class Solution:
    def minSumOfLengths(self, arr: List[int], target: int) -> int:
        tmp=[]
        hash=[]
        l=r=0
        tmp_sum=0
        for r in range(len(arr)):
            tmp_sum+=arr[r]
            while s>target:
                s-=arr[l]
                l+=1
            if s==target:
                tmp.append((l,r,r-l+1))
        tmp.sort(key=lambda x:x[2])
        if len(tmp) < 2:
            return -1
        else:
            t = tmp[0]
            for i in range(1,len(tmp)):
                if tmp[i][0]>t[1] or tmp[i][1]<t[0]:
                    return tmp[i][2]+t[2]
            return -1

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

 

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

1.滑动窗口+hash

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        lookup=set()
        left=right=0
        res=0
        while right<len(s):
            while s[right] in lookup:
                lookup.remove(s[left])
                left+=1
            lookup.add(s[right])
            right+=1
            res=max(res,right-left)
        return res

在一个由小写字母构成的字符串 S 中,包含由一些连续的相同字符所构成的分组。

例如,在字符串 S = "abbxxxxzyy" 中,就含有 "a", "bb", "xxxx", "z" 和 "yy" 这样的一些分组。

我们称所有包含大于或等于三个连续字符的分组为较大分组。找到每一个较大分组的起始和终止位置。

最终结果按照字典顺序输出。

示例 1:

输入: "abbxxxxzzy"
输出: [[3,6]]
解释: "xxxx" 是一个起始于 3 且终止于 6 的较大分组。

1.暴力

2.典型的滑动窗口

class Solution:
    def largeGroupPositions(self, S: str) -> List[List[int]]:
        res=[]
        t=0
        for i in range(len(S)):
            if i!=0 and i<=t:continue
            tmp=1
            for j in range(i+1,len(S)):
                if S[j]==S[i]:tmp+=1
                else:break
            if tmp>=3:
                t=i+tmp-1
                res.append([i,i+tmp-1])
        return res

class Solution:
    def largeGroupPositions(self, S: str) -> List[List[int]]:
        res=[]
        left=right=0
        while right<len(S):
            right+=1
            while right<len(S) and S[right]==S[left]:right+=1
            if right-1-left>=2:res.append([left,right-1])
            left=right
        return res

面试题 17.18. 最短超串

假设你有两个数组,一个长一个短,短的元素均不相同。找到长数组中包含短数组所有的元素的最短子数组,其出现顺序无关紧要。

返回最短子数组的左端点和右端点,如有多个满足条件的子数组,返回左端点最小的一个。若不存在,返回空数组。

示例 1:

输入:
big = [7,5,9,0,2,1,3,5,7,9,1,1,5,8,8,9,7]
small = [1,5,9]
输出: [7,10]

示例 2:

输入:
big = [1,2,3]
small = [4]
输出: []

提示:

    big.length <= 100000
    1 <= small.length <= 100000

1.滑动窗口+窗口内hash计数

class Solution:
    def shortestSeq(self, big: List[int], small: List[int]) -> List[int]:
        t=Counter(small)
        left=right=0
        res=float('inf')
        l=r=float('inf')
        lookup=defaultdict(int)
        while right<len(big):
            lookup[big[right]]+=1
            while all(map(lambda x:lookup[x]>=t[x],t.keys())):
                if right-left+1<res or (right-left+1==res and left<l):
                    res=min(res,right-left+1)
                    l=left
                    r=right
                lookup[big[left]]-=1
                left+=1
            right+=1
        return [l,r] if l!=float('inf') and r!=float('inf') else []


1208. 尽可能使字符串相等

给你两个长度相同的字符串,s 和 t。

将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。

用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。

如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。

如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。

 

示例 1:

输入:s = "abcd", t = "bcdf", cost = 3
输出:3
解释:s 中的 "abc" 可以变为 "bcd"。开销为 3,所以最大长度为 3。

class Solution:
    def equalSubstring(self, s: str, t: str, maxCost: int) -> int:
        n=len(s)
        tmp=[0 for _ in range(n)]
        for i in range(n):
            tmp[i]=abs(ord(s[i])-ord(t[i]))
        if maxCost<min(tmp):return 0
        left=right=0
        s=0
        res=0
        while right<n:
            s+=tmp[right]
            while s>maxCost and left<right:
                s-=tmp[left]
                left+=1
            res=max(res,right-left+1)
            right+=1
        return res
            
            
            

1658. 将 x 减到 0 的最小操作数

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

示例 1:

输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。

1.滑动窗口    ①求2边 ②求中间 

class Solution:
    def minOperations(self, nums: List[int], x: int) -> int:
        if sum(nums)<x:return -1
        n=len(nums)
        nums=nums+nums  
        check=[0,n-1,n,2*n-1]
        left=right=0
        s=0
        res=float('inf')
        while right<2*n:
            s+=nums[right]
            while s>x and left<right:
                s-=nums[left]
                left+=1 
            if s==x:
                flag=False 
                for c in check:
                    if left<=c<=right:
                        flag=True  
                if flag:
                    res=min(res,right-left+1)
            right+=1
        return res if res!=float('inf') else -1 


class Solution:
    def minOperations(self, nums: List[int], x: int) -> int:
        if sum(nums) == x:return len(nums)
        n=len(nums)
        target=sum(nums)-x  
        res=float('inf')
        left=right=0
        s=0
        while right<n:
            s+=nums[right]
            while s>target and left<right:
                s-=nums[left]
                left+=1
            if s==target:
                res=min(res,n - (right-left+1))
            right+=1
        return res if res!=float('inf') else -1 

1438. 绝对差不超过限制的最长连续子数组

给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。

如果不存在满足条件的子数组,则返回 0 。

 

示例 1:

输入:nums = [8,2,4,7], limit = 4
输出:2 
解释:所有子数组如下:
[8] 最大绝对差 |8-8| = 0 <= 4.
[8,2] 最大绝对差 |8-2| = 6 > 4. 
[8,2,4] 最大绝对差 |8-2| = 6 > 4.
[8,2,4,7] 最大绝对差 |8-2| = 6 > 4.
[2] 最大绝对差 |2-2| = 0 <= 4.
[2,4] 最大绝对差 |2-4| = 2 <= 4.
[2,4,7] 最大绝对差 |2-7| = 5 > 4.
[4] 最大绝对差 |4-4| = 0 <= 4.
[4,7] 最大绝对差 |4-7| = 3 <= 4.
[7] 最大绝对差 |7-7| = 0 <= 4. 
因此,满足题意的最长子数组的长度为 2 。
示例 2:

输入:nums = [10,1,2,4,7,2], limit =

1.2个单调队列获取最大和最小值,然后滑动窗口

class Solution:
    def longestSubarray(self, nums: List[int], limit: int) -> int:
        q_max=[]
        q_min=[]
        res=0 
        left=0
        right=0
        while right<len(nums):
            while q_max and nums[right]>nums[q_max[-1]]:q_max.pop()
            while q_min and nums[right]<nums[q_min[-1]]:q_min.pop()
            q_max.append(right)
            q_min.append(right)
            while abs(nums[q_max[0]]-nums[q_min[0]])>limit:
                if q_max[0]<=left:q_max.pop(0)
                if q_min[0]<=left:q_min.pop(0)
                left+=1  
            res=max(res,right-left+1)
            right+=1
        return res

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值