美文网首页
Python算法-滑动窗口(Sliding Window)

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

作者: ShowMeCoding | 来源:发表于2021-12-18 21:49 被阅读0次

    滑动窗口

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

          本文链接:https://www.haomeiwen.com/subject/axomvltx.html