Python算法-滑动窗口(Sliding Window)

滑动窗口

  • 1 减少while循环
  • 2 数组定长问题
3. 无重复字符的最长子串

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

  • 哈希表+双指针
# 双指针
class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        left = 0
        right = 0
        result = 0
        window = {}

        while right < len(s):
            if s[right] not in window:
                window[s[right]] = 1
            else:
                window[s[right]] += 1
            # 缩减窗口
            while window[s[right]] > 1:
                window[s[left]] -= 1
                left += 1
            # 维护一个最大值
            result = max(result, right - left + 1)
            right += 1
        return result
209. 长度最小的子数组

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

# 双指针
class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        left = 0
        right = 0
        window_sum = 0
        result = len(nums) + 1

        while right < len(nums):
            window_sum += nums[right]
            while window_sum >= target:
                result = min(result, right-left+1)
                window_sum -= nums[left]
                left += 1

            right += 1
        if result != len(nums) + 1:
            return result
        else:
            return 0
713. 乘积小于K的子数组

给定一个正整数数组 nums和整数 k 。
请找出该数组内乘积小于 k 的连续的子数组的个数。

输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8个乘积小于100的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于100的子数组。

输入: nums = [1,2,3], k = 0
输出: 0

# 双指针
class Solution:
    def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
        if k <= 1:
            return 0
        left = 0
        right = 0
        window_sum = 1
        result = 0

        while right < len(nums):
            window_sum *= nums[right]
            # 区间左侧边界向右
            while window_sum >= k:
                window_sum /= nums[left]
                left += 1

            # 区间右侧边界向右
            result += (right-left+1)   
            right += 1
        return result
1343. 大小为 K 且平均值大于等于阈值的子数组数目

输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4
输出:3
解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。

# 滑动窗口
class Solution:
    def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int:
        left = 0
        right = 0
        window_sum = 0
        result = 0

        while right < len(arr):
            # 更新窗口内数的求和
            window_sum += arr[right]
            if right - left + 1 >= k:
                # 判断窗口内数的和与平均值的和
                if window_sum >= k*threshold:
                    result += 1
                # 窗口左边界向右移动
                window_sum -= arr[left]
                left += 1 
            # 窗口右边界向右移动
            right += 1
        return result
643. 子数组最大平均数 I

输入:nums = [1,12,-5,-6,50,3], k = 4
输出:12.75
解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75

class Solution:
    def findLengthOfLCIS(self, nums: List[int]) -> int:
        if len(nums) == 0:
            return 0
        result = 1
        count = 1
        for i in range(len(nums)-1):
            if nums[i] < nums[i+1]:
                count += 1
            else:
                count = 1
            result = max(result, count)
        return result

674. 最长连续递增序列

输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。

class Solution:
    def findLengthOfLCIS(self, nums: List[int]) -> int:
        if len(nums) == 0:
            return 0
        result = 1
        count = 1
        for i in range(len(nums)-1):
            if nums[i] < nums[i+1]:
                count += 1
            else:
                count = 1
            result = max(result, count)
        return result
  • 双指针
# 双指针
class Solution:
    def findLengthOfLCIS(self, nums: List[int]) -> int:
        left = 0
        right = 0
        length = len(nums)
        result = 0
        for i in range(length):
            if nums[i] > nums[right]:
                right += 1
            else:
                left = i
                right = i
            result = max(result, right-left+1)
        return result
1004. 最大连续1的个数 III

输入: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。

class Solution:
    def longestOnes(self, nums: List[int], k: int) -> int:
        left = 0
        right = 0
        result = 0
        count = 0

        while right < len(nums):
            # 右边界右移
            if nums[right] == 0:
                count += 1
            right += 1
            # 左边界右移
            if count > k:
                if nums[left] == 0:
                    count -= 1
                left += 1
            # 更新最大值
            result = max(result, right-left)
        return result
1423. 可获得的最大点数

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

# 使用固定长度的滑动窗口
# 由于只能从开头或末尾位置拿 k 张牌,则最后剩下的肯定是连续的 len(cardPoints) - k 张牌。
# 要求求出 k 张牌可以获得的最大收益,我们可以反向先求出连续 len(cardPoints) - k 张牌的最小点数。
# 则答案为 sum(cardPoints) - min_sum。
class Solution:
    def maxScore(self, cardPoints: List[int], k: int) -> int:
        left = 0
        right = 0
        length = len(cardPoints)
        card_sum = sum(cardPoints)
        min_sum = card_sum
        window_size = length - k       # 窗口大小
        window_sum = 0
        if length == k:
            return card_sum

        while right < length:
            window_sum += cardPoints[right]
            if right - left + 1 >= window_size:
                min_sum = min(min_sum, window_sum)
                window_sum -= cardPoints[left]
                left += 1

            right += 1
        return card_sum - min_sum
1052. 爱生气的书店老板

今天,书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。
在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。
书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。
请你返回这一天营业下来,最多有多少客户能够感到满意。

输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], X = 3
输出:16
解释:
书店老板在最后 3 分钟保持冷静。
感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.

# 滑动窗口
# 窗口大小 X
# 固定长度的滑动窗口题目。我们可以维护一个窗口大小为 minutes 的滑动窗口。
# 使用 window_count 记录当前窗口内生气的顾客人数。
# 然后滑动求出窗口中最大顾客数,然后累加上老板未生气时的顾客数,就是答案。
class Solution:
    def maxSatisfied(self, customers: List[int], grumpy: List[int], minutes: int) -> int:
        left = 0
        right = 0
        result = 0
        window_sum = 0

        while right < len(customers):
            # 求窗口内最多的生气顾客数
            if grumpy[right] == 1:
                window_sum += customers[right]
            # 窗口左边界右移
            if right - left + 1 > minutes:
                if grumpy[left] == 1:
                    window_sum -= customers[left]
                left += 1
                
            right += 1
            result = max(result, window_sum)
            # print(result)

            # 求没有生气的顾客数量
        for i in range(len(customers)):
            if grumpy[i] == 0:
                result += customers[i]
        return result

1456. 定长子串中元音的最大数目

给你字符串 s 和整数 k 。
请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a, e, i, o, u)。
输入:s = "abciiidef", k = 3
输出:3
解释:子字符串 "iii" 包含 3 个元音字母。

# 定长窗口
class Solution:
    def maxVowels(self, s: str, k: int) -> int:
        hashmap = ['a','e','i','o','u']
        left = 0
        right = 0
        count = 0
        result = 0

        while right < len(s):
            if s[right] in hashmap:
                count += 1
            if right - left + 1 > k:
                if s[left] in hashmap:
                    count -= 1
                left += 1

            result = max(result, count)
            right += 1
        return result
567. 字符串的排列

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。
换句话说,s1 的排列之一是 s2 的 子串 。

输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").

# 排列关系如何处理?
# 用 collections.Counter() 实现对数组中元素个数的统计

import collections 
class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        left = 0
        right = 0
        s1_count = collections.Counter(s1)
        window_count = collections.Counter()
        window_size = len(s1)

        while right < len(s2):
            window_count[s2[right]] += 1
            # 窗口变小
            if right - left + 1 >= window_size:
                if window_count == s1_count:
                    return True
                window_count[s2[left]] -= 1
                if window_count[s2[left]] == 0:
                    del window_count[s2[left]]
                left += 1
            right += 1
        return False
438. 找到字符串中所有字母异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

# 字典+滑动窗口
import collections
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        # 创建数组p的字典
        p_dict = collections.defaultdict(int)
        for i in p:
            p_dict[i] += 1
        # 变量初始化
        left = 0
        right = 0
        # 创建滑动窗口的字典
        window_dict = collections.defaultdict(int)
        window_size = len(p)
        count = 0              # 统计相同字符的个数
        result = []
        # 滑动窗口实现
        while right < len(s):
            if s[right] in p_dict:
                window_dict[s[right]] += 1
                if window_dict[s[right]] == p_dict[s[right]]:
                    count += 1
            # 窗口左边界右移
            if right - left + 1 >= window_size:
                # 如果窗口内元素形成的字典与目标数组的字典相等
                if count == len(p_dict):
                    result.append(left)
                # 还要考虑窗口左边界右移是否会移除目标元素
                if s[left] in p_dict:
                    if window_dict[s[left]] == p_dict[s[left]]:
                        count -= 1
                    window_dict[s[left]] -= 1
                left += 1
            right += 1
        return result
995. K 连续位的最小翻转次数(困难)

在仅包含 0 和 1 的数组 A 中,一次 K 位翻转包括选择一个长度为 K 的(连续)子数组,同时将子数组中的每个 0 更改为 1,而每个 1 更改为 0。
返回所需的 K 位翻转的最小次数,以便数组没有值为 0 的元素。如果不可能,返回 -1。

输入:A = [0,1,0], K = 1
输出:2
解释:先翻转 A[0],然后翻转 A[2]。

class Solution:
    def minKBitFlips(self, nums: List[int], k: int) -> int:
        result = 0
        flip_count = 0
        for i in range(len(nums)):
            # 如果 i - k >= 0,并且 nums[i - k] == 2,需要缩小窗口,将翻转次数减一。
            # (此时窗口范围为 [i - k + 1, i - 1])
            if i - k >= 0 and nums[i-k] == 2:
                flip_count -= 1
            # 如果 (flip_count + nums[i]) % 2 == 0,则说明 nums[i] 还需要再翻转一次,
            # 将 nums[i] 标记为 2,同时更新窗口内翻转次数 flip_count 和答案最小翻转次数 ans。
            if (flip_count + nums[i])%2 == 0:
                if i + k > len(nums):
                    return -1
                nums[i] = 2
                flip_count += 1
                result += 1
        return result

例题

209:长度最小的子数组
输入:s = 7 , nums=[2,3,1,2,4,3]
输出:2 (找出该数组中满足其和 >= s 的长度最小的 连续子数组,返回其长度)
解释:子数组[4,3]是该条件下的长度最小的子数组

def findSub(s, nums):
    if nums == None or len(nums) == 0:
        return 0

    res = len(nums) + 1
    total = 0          # 子数组的和
    i = 0
    j = 0
    while j < len(nums):
        total = total + nums[j]             # 右指针向右滑动
        j = j + 1
        while total >= s:
            res = min(res, j-i)              # 注意是 j - i
            total = total - nums[i]        # 左指针向右移动
            i = i + 1
    if res == len(nums) + 1:           # 给定一个不可能的值,表示没找到
        return 0
    else:
        return res

s = 7 
nums=[2,3,1,2,4,3]
print(findSub(s, nums))

1456:定长字串中 元音字母 的最大数目
输入:s = 'abciiidef' k = 3(子串定长为3)
输出:3
解释:子字符串 'iii' 包含 3 个元音字母

def countSub(s, k):
    if s == None or len(s) == 0 or len(s) < k:
        return 0
    
    # 元音字母哈希
    hashset = {'a', 'e', 'i', 'o', 'u'}
    res = 0
    count = 0
    for i in range(k):          # 窗口为 k
        if s[i] in hashset:
            count += 1
        res = max(res, count)
    for i in range(k, len(s)):
        if s[i-k] in hashset:   # 窗口右移
            count -= 1
        if s[i] in hashset:
            count += 1
        res = max(res, count)

    return res

s = 'abciiidef'   
k = 3
print(countSub(s, k))
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容