leetcode 刷题(python)--1. Pattern: Sliding window,滑动窗口类型

1. Pattern: Sliding window,滑动窗口类型

滑动窗口类型的题目经常是用来执行数组或是链表上某个区间(窗口)上的操作。比如找最长的全为1的子数组长度。滑动窗口一般从第一个元素开始,一直往右边一个一个元素挪动。当然了,根据题目要求,我们可能有固定窗口大小的情况,也有窗口的大小变化的情况。
在这里插入图片描述
该图中,我们的窗子不断往右一格一个移动

下面是一些我们用来判断我们可能需要上滑动窗口策略的方法:

这个问题的输入是一些线性结构:比如链表呀,数组啊,字符串啊之类的
让你去求最长/最短子字符串或是某些特定的长度要求

经典题目:

Maximum Sum Subarray of Size K (easy)

给一个数组nums和一个目标值k,找出子数组和是k的最大长度,如果没有返回0. 要求O(n)时间复杂度。
Given nums = [1, -1, 5, -2, 3], k = 3,
return 4. (because the subarray [1, -1, 5, -2] sums to 3 and is the longest)

Given nums = [-2, -1, 2, 1], k = 1,
return 2. (because the subarray [-1, 2] sums to 1 and is the longest)

思路:

一个hash表,其key等于数组中遍历过的数的和,其value等于当前遍历的数在数组中的下标。用sum保存遍历过的数的总和,len保存最大长度;依次从数组的开头遍历,如果sum-k的值在hash表中有记录,则len更新为i-map[sum-k]和len的较大值,如果不存在就将{sum,i}加入map中,以便下次查询。
简单的说就好比有一个数组A,然后我们新建一个数组B,其中数组B中每一项B[i]保存A中[0…i]的和。那么对应的就会有前缀积,后缀和,后缀积。回到这道题目,我们可以使用两个for循环对该问题进行暴力破解,但是这不是leetcode(或者面试官们)想要的答案。那么面对诸如此类的问题我们就可以考虑使用前缀和方法以及哈希完成低时间负责的操作。我们以示例1(nums = [1, -1, 5, -2, 3], k = 3)来图解分析下:
在这里插入图片描述
数组A为原数组nums,B为A的前缀数组,为了方便理解与计算我们将前缀数组的头部加上一个元素,并设定为0,这样最终的前缀数组就是右边的样子了(可以理解吧,如果你不明白为什么加上一个0,往后面看),现在我们来看一下如何利用前缀数组解答这道题目。我们首先对这道题目降维,就是说我们先去求出nums数组中哪段子数组的和为K,依然图解:
在这里插入图片描述

假设有一个包含7个元素的数组,索引为0-6。我们可以求出每个元素对应的前缀和sum(index=1是,可以求出sum1)。如果当我们计算到sum5时发现sum5与k值只相差sum1,那么就可以证明2-5的和就是K值,就是我们要的子数组。即:sum5 = sum1 + k =====> sum5 - k = sum1。但是我们怎么保证能够发现之间的前缀有sum1呢?这时候哈希表就派上用场了,我们将sum作为key,将index作为valu

class Solution(object):
    def maxSubArrayLen(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        sums = {}
        cur_sum, max_len = 0, 0
        for i in xrange(len(nums)):
            cur_sum += nums[i]
            if cur_sum == k:
                max_len = i + 1
            elif cur_sum - k in sums:
                max_len = max(max_len, i - sums[cur_sum - k])
            if cur_sum not in sums:
                sums[cur_sum] = i  # Only keep the smallest index.
        return max_len 

https://www.jianshu.com/p/ad5d6c66b372

Smallest Subarray with a given sum (easy)

长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。

示例:
输入: s = 7, nums = [2,3,1,2,4,3],输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
进阶:
如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。

解法1:最容易想到的方法,直接遍历所有可能的组合。时间复杂度:O(n^2)

class Solution(object):
    def minSubArrayLen(self, s, nums):
        """
        :type s: int
        :type nums: List[int]
        :rtype: int
        """
 
        if not nums:
            return 0
        li = []
        for i in range(len(nums)-1):
            for j in range(i+1, len(nums)+1):
                # print(nums[i:j])
                if sum(nums[i:j]) >= s:
                    li.append(len(nums[i:j]))
        # print(li)
        if len(li) == 0:
            return 0
        else:
            return min(li)

解法3: 双指针,或对撞指针。不断判断跟s的大小,如果大了,说明元素太多,左移;反之小了,右移。时间复杂度:O(n)
初始化,最小长度为min_val = n,当前总和cur_sum=0,
双指针,计算start到end之间的和:
若cur_sum s,end + 1
若cur_sum s,将end-start+1与min_val中更小的值赋给min_val
将 cur_sum-nums[start]且start+1,直到cur_sum<s

class Solution(object):
    def minSubArrayLen(self, s, nums):
        """
        :type s: int
        :type nums: List[int]
        :rtype: int
        """
        if not nums:
            return 0
 
        # 创建2个指针
        pointer1, pointer2 = 0, 0
 
        total = 0
        nums_len = len(nums)
        minL = nums_len + 1
        while pointer1 < nums_len:
            if pointer2 < nums_len and total < s:
                total += nums[pointer2]
                pointer2 += 1
            else:
                total -= nums[pointer1]
                pointer1 += 1
 
            if total >= s:
                minL = min(minL, pointer2-pointer1)
 
        if minL == nums_len + 1:
            return 0
 
        return minL

解法5:动态规划法,不需要每次循环都计算加和。时间复杂度:O(nlog(n))。

class Solution(object):
    def minSubArrayLen(self, s, nums):
        """
        :type s: int
        :type nums: List[int]
        :rtype: int
        """
        if not nums:
            return 0
        res = len(nums) + 1
        low, high = 0, -1
        subsum = 0
        while (low < len(nums)):
            # 
            if (subsum < s) and (high+1 < len(nums)):
                high += 1
                subsum += nums[high]
            else:
                subsum -= nums[low]
                low += 1
 
            if subsum >= s:
                res = min(res, high - low + 1)
        return res if res != len(nums) + 1 else 0

Longest Longest Substring with K Distinct Characters (medium)至多包含K个不同字符的最长子串

给定一个字符串 s ,找出 至多 包含 k 个不同字符的最长子串 T。
示例 1:
输入: s = “eceba”, k = 2
输出: 3
解释: 则 T 为 “ece”,所以长度为 3。
示例 2:
输入: s = “aa”, k = 1
输出: 2
解释: 则 T 为 “aa”,所以长度为 2。

解题思路
滑动窗口思想,初始时左右指针都指向0,移动右指针,用一个字典window记录当前窗口内出现的字符的次数,并用一个变量count记录窗口内不同字符的数量。当count>k时,缩紧窗口,向右移动左指针,并更新window内字符数量和count。当count = k时,求结果。

class Solution(object):
    def lengthOfLongestSubstringKDistinct(self, s, k):
        """
        :type s: str
        :type k: int
        :rtype: int
        """
        """
        """
        left = 0
        right = 0
        window = {}
        count = 0
        res = 0
        if len(set(s)) < k:
            return len(s)
        while right < len(s):
            if window.get(s[right],0)==0:
                count += 1
            window[s[right]] = window.get(s[right],0) + 1
            while count > k:
                if window[s[left]] == 1:
                    count -= 1
                window[s[left]] = window[s[left]] - 1
                left += 1
            if count == k:
                    res = max(res,right-left+1)
            right += 1
        return res

https://zhuanlan.zhihu.com/p/104983442

Substring with K Distinct Characters (medium)

Fruits into Baskets (medium)
No-repeat Substring (hard)
Longest Substring with Same Letters after Replacement (hard)
Longest Subarray with Ones after Replacement (hard)

https://zhuanlan.zhihu.com/p/104983442

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值