leetcode—8.同向双指针—滑动窗口题型python解答

文章目录

      • 引言
      • 1.固定窗口
        • 239. 滑动窗口最大值
      • 2.可变窗口
        • 209. 长度最小的子数组
        • 713. 乘积小于K的子数组
        • 904. 水果成篮

引言

  有些时候,我们需要获得数组或者字符串的连续子部分,这时候我们就可以考虑使用滑动窗口。nums[left,right]为滑动窗口,根据具体的要求,通过遍历的时候来改变 left和right 的位置,从而完成任务。滑动窗口主要处理连续问题,按类型主要有如下三种:

  1. 固定窗口大小
  2. 窗口大小不固定,求解最大的满足条件的窗口
  3. 窗口大小不固定,求解最小的满足条件的窗口

1.固定窗口

对于固定窗口,我们只需要固定初始化左右指针Ieft和right,分别表示的窗口的左右顶点,并且保证:
1、Ieft初始化为0,
2、初始化right,使得right -Ieft+1等于窗口大小,
3、同时移动Ieft和right,
4、判断窗口内的连续元素是否满足题目限定的条件。
4.1、如果满足,再判断是否需要更新最优解,如果需要则更新最优解。
4.2、如果不满足,则继续。

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。

示例:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        # 定义左右指针
        left,right = 0,k
        res =[]  # 存储滑动窗口内的最大值
        if len(nums) > 0:
            while right <= len(nums):
                maxValues = max(nums[left:right])
                res.append(maxValues)
                left += 1
                right += 1
            return res
        else:
            return nums

leetcode—8.同向双指针—滑动窗口题型python解答_第1张图片
239. 滑动窗口最大值,利用双端队列一次遍历

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        res = []  # 存储结果
        queue = collections.deque()  # 双端队列,两端快速添加(append)和弹出(pop)
        for i, num in enumerate(nums):
            if queue and queue[0] == i - k:
                queue.popleft()
            while queue and nums[queue[-1]] < num:
                queue.pop()
            queue.append(i)
            if i >= k - 1:
                res.append(nums[queue[0]])

        return res

2.可变窗口

对于可变窗口,我们同样固定初始化左右指针,分别表示的窗口的左右顶点。后面有所不同,我们需要保证:
1、Ieft和right都初始化为0
2、right指针移动一步
3、判断窗口内的连续元素是否满足题目限定的条件
3.1如果满足,再判断是否需要更新最优解,如果需要则更新最优解。并尝试通过移动Ieft指针缩小窗口大小。循环执行3.1
3.2如果不满足,则继续。

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [ n u m s l , n u m s l + 1 , . . . , n u m s r − 1 , n u m s r ] [nums_l, nums_{l+1}, ..., nums_{r-1}, nums_r] [numsl,numsl+1,...,numsr1,numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0

思路:

1、开始right向右滑动,使和变大。
2、当恰好>=s时,记录滑窗所包括的子数组长度res,若res 已有数值,需判断新值是否小于旧值,若是,更新res; left向右滑动。
3、判断是否仍>=s,若是,重复步骤2,3。若否,转步骤1。直到右边框到达最右边

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        # 初始化
        left = 0
        sums = 0
        res = float('inf')  # 无穷大
        # 右指针移动
        for right in range(len(nums)):
            sums += nums[right]
            while sums >= target:
                # 判断新值是否小于旧值,如果是则更新
                if right - left + 1 < res:
                    res = right - left + 1
                # 左指针向右移动
                sums -= nums[left]
                left += 1
            # 循环结束条件:sums
        if res == float('inf'):
            return 0
        else:
            return res

713. 乘积小于K的子数组

给定一个正整数数组 nums。
找出该数组内乘积小于 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的子数组。

思路:

步骤1:当left <= right且滑动窗口内的乘积小于k时,我们可以知道[Ieft,right].[left+1,right]…[right-1,right]均满足条件,因此,计数加right-left+1,然后移动右边界(滑动区间加大),看剩下的区间是否满足乘积小于k,如果小于k,重复步骤1,否则进行步骤2。
步骤2:当滑动窗口内的乘积大于等于k时,右移左边界(滑动区间减小),如果这个区间内乘积小于k,进入步骤1,否则重复步骤2

class Solution:
    def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
        # 初始化
        left = 0
        product = 1
        res = 0
        # 右指针先移动
        for right in range(len(nums)):
            product *= nums[right]
            # 乘积大于等于k时,移动左指针
            while left <= right and product >= k:
                product /= nums[left]
                left += 1
            # 循环结束条件为:product < k
            # 统计当前右边界下的子数组个数
            res += (right - left + 1)
        return res

904. 水果成篮

在一排树中,第 i 棵树产生 tree[i] 型的水果。
你可以从你选择的任何树开始,然后重复执行以下步骤:

  1. 把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。
  2. 移动到当前树右侧的下一棵树。如果右边没有树,就停下来。

请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。
你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。
用这个程序你能收集的水果树的最大总量是多少?

问题可以抽象为:要求一个最长的连续子序列,同时这个子序列的元素种类不能超过2种

用一个右指针right和左指针left。右指针用于往右探,左指针用于指示每一个新的子序列要从前面那个序列里继承哪一位。那么继承哪一位呢?实际上继承的是翻转的那一位。例如[1,2,1,3,3,3],right往右遍历的时候,如果当前的种类与left的种类不同,那么就令left=right,其中i就指示了下一个子序列的起点。

class Solution:
    def totalFruit(self, tree: List[int]) -> int:
        ans = 0 # 总量
        n = len(tree)
        left, right = 0, 0
        types = {
     }  # 篮子
        while right < n:
            # 当前水果不可加入
            if len(types) == 2 and types.get(tree[right]) is None:    
                types = {
     }
                right = left                           # j回退到最后一次翻转的位置
            else:  # 当前水果可加入
                types[tree[right]] = types.get(tree[right], 0) + 1
                if tree[right] != tree[left]:     # 水果有翻转 用i记录下翻转的位置
                    left = right
                right += 1
            ans = max(ans, sum(types.values()))
        return ans

思路:

模拟一个滑动窗口,维护变量 i 是最小的下标满足 [i, j] 是合法的子序列。
维护 count 是序列中各种类型的个数,这使得我们可以很快知道子序列中是否含有 3 种类型。

class Solution(object):
    def totalFruit(self, tree):
        ans = i = 0
        # 字典的子类,提供了可哈希对象的计数功能
        count = collections.Counter()
        for j, x in enumerate(tree):
            count[x] += 1
            while len(count) > 2:
                # i指针右移
                count[tree[i]] -= 1
                if count[tree[i]] == 0:
                    del count[tree[i]]
                i += 1
            
            ans = max(ans, j - i + 1)
        return ans

leetcode—8.同向双指针—滑动窗口题型python解答_第2张图片

你可能感兴趣的:(#,LeetCode刷题,滑动窗口,python,leetcode,固定窗口,可变窗口)