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))

你可能感兴趣的:(Python算法-滑动窗口(Sliding Window))