移动窗口问题

先给出labuladong框架

/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
    unordered_map need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0; 
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 右移窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...

        

        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            // 左移窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}

下面有几道典型的窗口题,前面几道是很容易套这个模板的。但是后面的两道:求和和求乘积,并不是典型的窗口题,或者说,有一点点小变化。总体遵循的思想应该是:1. 两个while循环(或者第一个循环用for)2. 想清楚什么时候应该收缩左边窗口 3. 想清楚什么时候应该往res返回结果里添加值。

最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

示例 2:

输入:s = "a", t = "a"
输出:"a"

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        need = collections.defaultdict(int) # 记录t中字符出现次数
        window = collections.defaultdict(int) # 记录窗口中响应的字符出现的次数
        for c in t:
            need[c] += 1
        
        left,right = 0,0 # 初始窗口长度为0
        valid = 0 # 用于记录window中t中字符是否出现完,比如:t='abc',window='abd',valid就等于2.代表need中应该出现的字符在window中才出现了两个,还没有出现完全

        # 记录最小覆盖子串的起始索引及长度
        start = 0
        length = float('inf')

        while right < len(s):
            c = s[right] # 即将加入window的字符c
            right += 1 # 右移窗口

            # 窗口内数据的一系列更新
            if c in need:
                window[c] += 1
                if window[c] == need[c]: # window中字符c出现的次数已经达到need所需要的次数时,valid进行更新
                    valid += 1

            # 判断窗口左侧边界是否要收缩
            while valid == len(need):
                # 在这里更新最小覆盖子串
                if right-left < length:
                    start = left
                    length = right-left

                # d是将移出窗口的字符
                d = s[left]
                # 左移窗口
                left += 1
                # 进行窗口内数据的一系列更新
                if d in need:
                    if window[d] == need[d]: # 这句话和下面的window[c]-=1不能反,先判断删去的字符c的数量是不是满足need的数量,如果满足,valid将减去1。
                        valid -= 1
                    window[d] -= 1
        # 返回最小覆盖子串
        if length == float('inf'):
            return ''
        else:
            return s[start:start+length]

字符串的排列

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。

换句话说,第一个字符串的排列之一是第二个字符串的 子串 。

示例 1:

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

输入: s1= "ab" s2 = "eidboaoo"
输出: False

class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        # 问题转化为s2中是否存在一个子串,使得该子串包含s1中所有的元素,而且不包含其他字符。
        need = collections.defaultdict(int) # 记录t中字符出现次数
        window = collections.defaultdict(int) # 记录窗口中响应的字符出现的次数
        for c in s1:
            need[c] += 1
        
        left,right = 0,0 # 初始窗口长度为0
        valid = 0 # 用于记录window中t中字符是否出现完,比如:t='abc',window='abd',valid就等于2.代表need中应该出现的字符在window中才出现了两个,还没有出现完全

        # 记录最小覆盖子串的起始索引及长度
        start = 0
        length = float('inf')

        while right < len(s2):
            c = s2[right] # 即将加入window的字符c
            right += 1 # 右移窗口

            # 窗口内数据的一系列更新
            if c in need:
                window[c] += 1
                if window[c] == need[c]: # window中字符c出现的次数已经达到need所需要的次数时,valid进行更新
                    valid += 1

            # 判断窗口左侧边界是否要收缩(当窗口内的字符数量大于该有的字符数量时,左边需要收缩)
            while right-left >= len(s1):
                # 在这里判断是否找到了合法子串
                if valid == len(need):
                    return True

                # d是将移出窗口的字符
                d = s2[left]
                # 左移窗口
                left += 1
                # 进行窗口内数据的一系列更新
                if d in need:
                    if window[d] == need[d]: # 这句话和下面的window[c]-=1不能反,先判断删去的字符c的数量是不是满足need的数量,如果满足,valid将减去1。
                        valid -= 1
                    window[d] -= 1
                    
        return False

无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

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

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

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
示例 4:

输入: s = ""
输出: 0

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        
        max_length = 0
        window = collections.defaultdict(int)

        left,right = 0,0
    
        while right < len(s):
            c = s[right]
            right += 1
            window[c] += 1
            
            # 当window中有重复字符的时候开始收缩左边界
            while window[c] > 1: 
                d = s[left]
                left += 1
                window[d] -= 1
            
            max_length = max(max_length,right-left)
        
        return max_length

要在收缩窗口完成后更新 res,因为窗口收缩的 while 条件是存在重复元素。

至多包含K个不同字符的最长子串

给定一个字符串 s ,找出 至多 包含 k 个不同字符的最长子串 T。

示例 1:

输入: s = "eceba", k = 2
输出: 3
解释: 则 T 为 "ece",所以长度为 3。
示例 2:

输入: s = "aa", k = 1
输出: 2
解释: 则 T 为 "aa",所以长度为 2。

class Solution:
    def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -> int:
        max_length = 0 
        left,right = 0,0
        valid = 0
        window = collections.defaultdict(int)
        while right < len(s):
            c = s[right]
            right += 1
            
            if window[c] == 0:
                valid += 1
            window[c] += 1

            # 当窗口内字符串包含多于k个不同字符时,收缩窗口左边边界
            while valid > k :
                d = s[left]
                left += 1

                if window[d] == 1:
                    valid -= 1
                window[d] -= 1
            
            max_length = max(max_length,right-left)
        
        return max_length

438.找到字符串中的所有字母异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指字母相同,但排列不同的字符串。

示例 1:

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

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        window = collections.defaultdict(int)
        need = collections.defaultdict(int)
        for ch in p:
            need[ch] += 1
        res = [] # 存储最终返回结果,即各子串的起始index
        left,right= 0,0
        length = len(p) + 1
        valid = 0
        
        while right < len(s):
            # 准备移入窗口的字符
            c = s[right]
            right += 1

            if c in need:
                window[c] += 1
                if window[c] == need[c]:
                    valid +=1
            
            # 判断何时需要收缩左边窗口边界
            while right-left >= len(p):
                if valid == len(need):
                    res.append(left)
                # 即将挤出窗口的字符d
                d = s[left]
                left += 1
                
                if d in need:
                    if window[d] == need[d]:
                        valid -=1
                    window[d] -= 1
        
        return res

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:

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

输入:target = 4, nums = [1,4,4]
输出:1
示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:

        windows = defaultdict(int)
        left , right = 0,0

        n = len(nums)
        res = float('inf')
        s = 0
        while right < n:
            num = nums[right]
            
            s += num

            while s >= target:
                res = min(res,right-left+1)
                num = nums[left]
                left += 1               
                s -= num
            
            right += 1

        if res != float('inf') :
            return res
        else:
            return 0

731.乘积小于K的子数组

给定一个正整数数组 nums和整数 k 。

请找出该数组内乘积小于 k 的连续的子数组的个数。

示例 1:

输入: 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的子数组。
示例 2:

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

class Solution:
    def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
        if k <= 1: return 0
        res = 0
        left = 0
        right = 0
        s = 1
        while right < len(nums):
            num = nums[right]                      
            s = s * num
                       
            while s >= k :
                num = nums[left]
                s = s / num
                left += 1
                
            res += (right-left) + 1
            right += 1
               
        return res

你可能感兴趣的:(移动窗口问题)