Python数据结构与算法篇-- 滑动窗口算法

Posted 长路漫漫2021

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python数据结构与算法篇-- 滑动窗口算法相关的知识,希望对你有一定的参考价值。

        数组和链表代表着计算机最基本的两种存储形式:顺序存储和链式存储,所以他俩可以算是最基本的数据结构。数组是一种基础数据结构,可以用来处理常见的排序和二分搜索问题,典型的处理技巧包括双指针、滑动窗口等,数组是数据结构中的基本模块之一。因为字符串是由字符数组形成的,所以二者是相似的。

1 滑动窗口

1.1 定义

        在计算机网络里经常用到滑动窗口协议(Sliding Window Protocol),该协议是 TCP协议 的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认。因此该协议可以加速数据的传输,提高网络吞吐量。

        滑动窗口算法其实和这个是一样的,只是用的地方场景不一样,可以根据需要调整窗口的大小,有时也可以是固定窗口大小。

        滑动窗口使用双指针解决问题,所以一般也叫双指针算法,因为两个指针间形成一个窗口。双指针也并不局限在数组问题,像链表场景的 “快慢指针” 也属于双指针的场景,其快慢指针滑动过程中本身就会产生一个窗口,比如当窗口收缩到某种程度,可以得到一些结论。

        什么情况适合用滑动窗口算法呢?

  • 需要输出或比较的结果在原数据结构中是连续排列的,特别是数组或链表问题;
  • 每次窗口滑动时,只需观察窗口两端元素的变化,无论窗口多长,每次只操作两个头尾元素,当用到的窗口比较长时,可以显著减少操作次数;
  • 窗口内元素的整体性比较强,窗口滑动可以只通过操作头尾两个位置的变化实现,但对比结果时往往要用到窗口中所有元素。

        滑动窗口算法常用于字符串匹配问题和子数组问题。

        滑动窗口算法在一个特定大小的字符串或数组上进行操作,而不在整个字符串和数组上操作,这样就降低了问题的复杂度,从而也达到降低了循环的嵌套深度。其实这里就可以看出来滑动窗口主要应用在数组和字符串上。

1.2 滑动窗口法的大体框架

        在介绍滑动窗口的框架时候,大家先从字面理解下:

  • 滑动:说明这个窗口是移动的,也就是移动是按照一定方向来的。
  • 窗口:窗口大小并不是固定的,可以不断扩容直到满足一定的条件;也可以不断缩小,直到找到一个满足条件的最小窗口;当然也可以是固定大小。

        为了便于理解,这里采用的是字符串来讲解。但是对于数组其实也是一样的。滑动窗口算法的思路是这样:

  1. 我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。
  2. 我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
  3. 此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。
  4. 重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

        这个思路其实也不难,第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。

1.3 滑动窗口模板

滑窗模板 Python 伪代码

class Solution:
    def problemName(self, s: str) -> int:
        # Step 1: 定义需要维护的变量们 (对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表)
        x, y = ..., ...

        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end in range(len(s)):
            # Step 3: 更新需要维护的变量, 有的变量需要一个if语句来维护 (比如最大最小长度)
            x = new_x
            if condition:
                y = new_y

            '''
            ------------- 下面是两种情况,读者请根据题意二选1 -------------
            '''
            # Step 4 - 情况1
            # 如果题目的窗口长度固定:用一个if语句判断一下当前窗口长度是否达到了限定长度 
            # 如果达到了,窗口左指针前移一个单位,从而保证下一次右指针右移时,窗口长度保持不变, 
            # 左指针移动之前, 先更新Step 1定义的(部分或所有)维护变量 
            if 窗口长度达到了限定长度:
                # 更新 (部分或所有) 维护变量 
                # 窗口左指针前移一个单位保证下一次右指针右移时窗口长度保持不变

            # Step 4 - 情况2
            # 如果题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题
            # 如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
            # 在左指针移动之前更新Step 1定义的(部分或所有)维护变量 
            while 不合法:
                # 更新 (部分或所有) 维护变量 
                # 不断移动窗口左指针直到窗口再次合法

        # Step 5: 返回答案
        return ...

        滑动窗口中用到了左右两个指针,它们移动的思路是:以右指针(end)作为驱动,拖着左指针(start)向前走。右指针每次只移动一步,而左指针在内部 while 循环中每次可能移动多步。右指针是主动前移,探索未知的新区域;左指针是被迫移动,负责寻找满足题意的区间。


2 常见题型

2.1 题库列表

2.2 真题演练

643. 子数组最大平均数 I
        题目描述:给你一个由 n 个元素组成的整数数组 nums 和一个整数 k。请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。

class Solution:
    def findMaxAverage(self, nums: List[int], k: int) -> float:
        # Step 1
        # 定义需要维护的变量
        # 本题求最大平均值 (其实就是求最大和),所以需要定义sum_sub_array, 同时定义一个max_value (初始值为负无穷)
        sum_sub_array, max_value = 0, float('-inf')

        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end, num in enumerate(nums):
            # Step 3: 更新需要维护的变量 (sum_sub_array, max_value), 不断把当前值积累到sum_sub_array上
            sum_sub_array += num
            if end - start + 1 == k:
                max_value = max(max_value, sum_sub_array)

            # Step 4
            # 根据题意可知窗口长度固定,所以用if
            # 窗口首指针前移一个单位保证窗口长度固定, 同时提前更新需要维护的变量 (sum_sub_array)
            if end >= k - 1:
                sum_sub_array -= nums[start]
                start += 1
        # Step 5: 返回答案
        return max_value/k

3. 无重复字符的最长子串

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

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        # Step 1: 定义需要维护的变量, 本题求最大长度,所以需要定义max_len, 该题又涉及去重,因此还需要一个哈希表
        max_len, hash_map = 0, 

        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end, tail in enumerate(s):
            # Step 3
            # 更新需要维护的变量 (max_len, hashmap)
            # i.e. 把窗口末端元素加入哈希表,使其频率加1,并且更新最大长度
            hash_map[tail] = hash_map.get(tail, 0) + 1
            if len(hash_map) == end - start + 1:
                max_len = max(max_len, end - start + 1)
            
            # Step 4: 
            # 根据题意,  题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题
            # 这时要用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
            # 当窗口长度大于哈希表长度时候 (说明存在重复元素),窗口不合法
            # 所以需要不断移动窗口左指针直到窗口再次合法, 同时提前更新需要维护的变量 (hashmap)
            while end - start + 1 > len(hash_map):
                head = s[start]
                hash_map[head] -= 1
                if hash_map[head] == 0:
                    del hash_map[head]
                start += 1
        # Step 5: 返回答案 (最大长度)
        return max_len

159. 至多包含两个不同字符的最长子串

        题目描述:给定一个字符串 s ,找出 至多 包含两个不同字符的最长子串 t 。

class Solution:
    def lengthOfLongestSubstringTwoDistinct(self, s: str) -> int:
        # Step 1: 
        # 定义需要维护的变量, 本题求最大长度,所以需要定义max_len,
        # 该题又涉及计算不重复元素个数,因此还需要一个哈希表
        max_len, hashmap = 0, 

        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end, tail in enumerate(s):
            # Step 3
            # 更新需要维护的变量 (max_len, hashmap)
            # 首先,把当前元素的计数加一
            # 一旦哈希表长度小于等于2(之多包含2个不同元素),尝试更新最大长度
            hashmap[tail] = hashmap.get(tail, 0) + 1
            if len(hashmap) <= 2:
                max_len = max(max_len, end - start + 1)

            # Step 4: 
            # 根据题意,  题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题
            # 这时要用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
            # 哈希表长度大于2的时候 (说明存在至少3个重复元素),窗口不合法
            # 所以需要不断移动窗口左指针直到窗口再次合法, 同时提前更新需要维护的变量 (hashmap)
            while len(hashmap) > 2:
                head = s[start]
                hashmap[head] -= 1
                if hashmap[head] == 0:
                    del hashmap[head]
                start += 1
        # Step 5: 返回答案 (最大长度)
        return max_len

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。

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        # Step 1: 定义需要维护的变量, 本题求最小长度,所以需要定义min_len, 本题又涉及求和,因此还需要一个sum变量
        min_len, sum_sub_array = math.inf, 0

        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end, num in enumerate(nums):
            # Step 3: 更新需要维护的变量 (min_len, sum_sub_array)
            sum_sub_array += num

            # 这一段可以删除,因为下面的while已经handle了这一块儿逻辑,不过写在这也没影响
            if sum_sub_array >= target:
                min_len = min(min_len, end - start + 1)

            # Step 4
            # 这一题这里稍微有一点特别: sum_sub_array >= target其实是合法的,但由于我们要求的是最小长度,
            # 所以当sum_sub_array已经大于target的时候继续移动右指针没有意义,因此还是需要移动左指针慢慢逼近答案
            # 由于左指针的移动可能影响min_len和sum_sub_array的值,因此需要在移动前将它们更新
            while sum_sub_array >= target:
                min_len = min(min_len, end - start + 1)
                sum_sub_array -= nums[start]
                start += 1
        # Step 5:返回答案 (最小长度)
        if min_len == math.inf:
            return 0
        return min_len

1695. 删除子数组的最大得分

        题目描述:给你一个正整数数组 nums ,请你从中删除一个含有 若干不同元素 的子数组。删除子数组的 得分 就是子数组各元素之 和。返回 只删除一个 子数组可获得的 最大得分。如果数组 b 是数组 a 的一个连续子序列,即如果它等于 a[l],a[l+1],…,a[r] ,那么它就是 a 的一个子数组。

class Solution:
    def maximumUniqueSubarray(self, nums: List[int]) -> int:
        # Step 1
        # 定义需要维护的变量, 本题最大得分,所以需要定义当前得分sum_sub_array和最大得分max_sum
        # 本题又涉及去重 (题目规定子数组不能有重复),因此还需要一个哈希表
        sum_sub_array, max_sum, hashmap = 0, 0, 

        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end, tail in enumerate(nums):
            # Step 3
            # 更新需要维护的变量 (sum_sub_array, hashmap)
            # sum和hashmap需要更新就不说了,max_sum当且仅当哈希表里面没有重复元素时 (end - start + 1 == len(hashmap)) 更新
            sum_sub_array += tail
            hashmap[tail] = hashmap.get(tail, 0) + 1
            if end - start + 1 == len(hashmap):
                max_sum = max(max_sum, sum_sub_array)
            
            # Step 4
            # 根据题意,  题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题
            # 这时要用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
            # 哈希表里面有重复元素时 (end - start + 1 > len(hashmap)) 窗口不合法
            # 所以需要不断移动窗口左指针直到窗口再次合法, 同时提前更新需要维护的变量 (hashmap, sum_sub_array)
            while end - start + 1 > len(hashmap):
                head = nums[start]
                hashmap[head] -= 1
                if hashmap[head] == 0:
                    del hashmap[head]
                sum_sub_array -= nums[start]
                start += 1
        # Step 5: 返回答案
        return max_sum

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

        题目描述:给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        # Step 1: 
        # 定义需要维护的变量
        # 本文需要对比两组字符串是否为异位词,所以用哈希表 (abc和bac是异位词是因为他们对应的哈希表相等)
        # 同时我们需要找到所有合法解,所以还需要一个ans数组
        ans, hashmap_s = [], 

        # Step 1.1: 同时把p的哈希表也建立了 (这个哈希表不需要维护,为定值)
        hashmap_p = Counter(p)

        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end, tail in enumerate(s):
            # Step 3: 更新需要维护的变量 (hashmap), 如果hashmap == hashmap_p,代表找到了一个解,加入到ans
            hashmap_s[tail] = hashmap_s.get(tail, 0) + 1
            if hashmap_s == hashmap_p:
                ans.append(start)

            # Step 4 
            # 根据题意可知窗口长度固定,所以用if
            # 窗口左指针前移一个单位保证窗口长度固定, 同时提前更新需要维护的变量 (hashmap)
            if end >= len(p) - 1:
                head = s[start]
                hashmap_s[head] -= 1
                if hashmap_s[head] == 0:
                    del hashmap_s[head]
                start += 1
        # Step 5: 返回答案
        return ans

567. 字符串的排列

        题目描述:给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。

class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        # Step 1
        # 定义需要维护的变量
        # 因为和排列相关 (元素相同,顺序可以不同),使用哈希表
        hash_map_s2 = 

        # Step 1.1: 同时建立s1的哈希表 (这个哈希表不需要维护,为定值)
        hash_map_s1 = Counter(s1)
        
        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end, tail in enumerate(s2):
            # Step 3: 更新需要维护的变量 (hash_map_s2), 如果hash_map_s1 == hash_map_s2,代表s2包含s1的排列,直接return
            hash_map_s2[tail] = hash_map_s2.get(tail, 0) + 1
            if hash_map_s1 == hash_map_s2:
                    return True

            # Step 4: 
            # 根据题意可知窗口长度固定,所以用if
            # 窗口左指针前移一个单位保证窗口长度固定, 同时提前更新需要维护的变量 (hash_map_s2)
            if end >= len(s1) - 1:
                head = s2[start]
                hash_map_s2[head] -= 1
                if hash_map_s2[head] == 0:
                    del hash_map_s2[head]
                start += 1
        # Step 5: 没有在s2中找到s1的排列,返回False
        return False

487. 最大连续1的个数 II

        题目描述:给定一个二进制数组,你可以最多将 1 个 0 翻转为 1,找出其中最大连续 1 的个数。

class Solution:
    def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
        # Step 1
        # 定义需要维护的变量
        # 因为是求最大长度,所以有max_len,又同时涉及计数 (0的个数不能超过1个),所以还要一个哈希表
        max_len, hash_map = 0, 

        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end, tail in enumerate(nums):
            # Step 3: 更新需要维护的变量 (hash_map, max_len)
            hash_map[tail] = hash_map.get(tail, 0) + 1
            if hash_map.get(0, 0) <= 1:
                max_len = max(max_len, end - start + 1)

            # Step 4
            # 根据题意,  题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题
            # 这时要用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
            # 当hash_map里面0的个数大于1的时候,窗口不合法
            # 所以需要不断移动窗口左指针直到窗口再次合法, 同时提前更新需要维护的变量 (hash_map)
            while hash_map.get(0, 0) > 1:
                head = nums[start]
                hash_map[head] -= 1
                start += 1
        # Step 5: 返回答案 (最大长度)
        return max_len

1004. 最大连续1的个数 III
        题目描述:给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。

class Solution:
    def longestOnes(self, nums: List[int], k: int) -> int:
        max_len, start = 0, 0
        hash_map = 
        for end, tail in enumerate(nums):
            hash_map[tail] = hash_map.get(tail, 0) + 1
            if hash_map.get(0, 0) <= k:     # 相比较于上一题,只需要把1改成k
                max_len = max(max_len, end-start+1)
            while hash_map.get(0, 0) > k:
                head = nums[start]
                hash_map[head] -= 1
                start += 1
        return max_len

1208. 尽可能使字符串相等
        题目描述:给你两个长度相同的字符串,s 和 t。将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。

class Solution:
    def equalSubstring(self, s: str, t: str, max_cost: int) -> int:
        # Step 1: 定义需要维护的变量
        # 因为是求最大长度,所以有max_len,又同时涉及计算开销 (和求和一个道理), 所以还要一个cur_cost
        cur_cost, max_len = 0, 0
        
        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end in range(len(t)):
            # Step 3
            # 更新需要维护的变量 (cur_cost)
            # 每一对字符的order差值就是当前时间点的开销,直接累积在cur_cost上即可
            # cur_cost只要不超过最大开销,就更新max_len

数据结构与算法:滑动窗口类题目总结

滑动窗口类型题目解题框架总结:

class Solution:
    def problemName(self, s: str) -> int:
        # Step 1: 定义需要维护的变量们 (对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表)
        x, y = ..., ...


        # Step 2: 定义窗口的首尾端 (start, end), 然后滑动窗口
        start = 0
        for end in range(len(s)):
            # Step 3: 更新需要维护的变量, 有的变量需要一个if语句来维护 (比如最大最小长度)
            x = new_x
            if condition:
                y = new_y


            '''
            ------------- 下面是两种情况,读者请根据题意二选1 -------------
            '''
            # Step 4 - 情况1
            # 如果题目的窗口长度固定:用一个if语句判断一下当前窗口长度是否达到了限定长度 
            # 如果达到了,窗口左指针前移一个单位,从而保证下一次右指针右移时,窗口长度保持不变, 
            # 左指针移动之前, 先更新Step 1定义的(部分或所有)维护变量 
            if 窗口长度达到了限定长度:
                # 更新 (部分或所有) 维护变量 
                # 窗口左指针前移一个单位保证下一次右指针右移时窗口长度保持不变


            # Step 4 - 情况2
            # 如果题目的窗口长度可变: 这个时候一般涉及到窗口是否合法的问题
            # 如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
            # 在左指针移动之前更新Step 1定义的(部分或所有)维护变量 
            while 不合法:
                # 更新 (部分或所有) 维护变量 
                # 不断移动窗口左指针直到窗口再次合法


        # Step 5: 返回答案
        return ...

1、无重复字符的最长子串(leetcode-003)

给定一个字符串 ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 :
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3// 双指针,滑动窗口(时间复杂度n)
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution 
        public int lengthOfLongestSubstring(String s) 

            //判断处理空字符
            if (s==null || s.length() == 0)
                return 0;
            
            // step1:定义需要维护的变量,最小子串数量,哈希表存储字符与字符位置
            int ans = 0;
            Map<Character, Integer> map = new HashMap<>();
            // step2:定义左右两个指针,滑动右指针
            for (int start = 0,end = 0;end<s.length();end++)
                char element = s.charAt(end);
                // step3:如果出现重复字符,则移动左指针,确保左指针必须右移,需要max筛选防止左移
                if (map.containsKey(element))
                    start = Math.max(start,map.get(element)+1);
                
                // step4:更新维护变量
                ans = Math.max(ans,end-start+1);
                map.put(element,end);
            
            return ans;
        
    
//leetcode submit region end(Prohibit modification and deletion)

2.子数组最大平均数 I(leetcode-643)

给你一个由n个元素组成的整数数组nums和一个整数k 。
请你找出平均数最大且 长度为k的连续子数组,并输出该最大平均数。
示例:
输入:nums = [1,12,-5,-6,50,3], k = 4
输出:12.75
解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75
    /**
     * 滑动窗口套路题
     */
    class Solution 
        public double findMaxAverage(int[] nums, int k) 
            //定义需要维护的变量,求和与平均值
            double sum = 0;
            double max_avg = -999999;
            // 定义窗口首末端,并且滑动末端窗口
            for (int start=0,end=0;end<nums.length;end++)
                sum+=nums[end];
                //更新需要维护的变量
                if (end-start+1==k)
                    max_avg = Math.max(max_avg,sum / k);
                
                // 窗口长度固定,则首指针前移,同时维护变量
                if (end>=k-1)
                    sum-=nums[start];
                    start+=1;
                
            
            //返回结果
            return max_avg;
        
    
//leetcode submit region end(Prohibit modification and deletion)

3.至多包含两个不同字符的最长子串(leetcode-159)

public static int lengthOfLongestSubstringTwoDistinct(String s) 
  //定义需要维护的变量,最长字串长度,map存储字符和位置
  int ans = 0;
  Map<Character,Integer> map = new HashMap<>();


  // 定义两个窗口两个指针,滑动末端指针
  for (int start=0,end=0;end<s.length();end++)
    //step3:更新维护变量,map存储字符与位置
    char element = s.charAt(end);
    map.put(element,end);
    if (map.size()<=2) 
      ans = Math.max(ans,end-start+1);
    
    // step4:如果哈希表长度等于 说明出现三个重复字符,则获取最左字符位置+1作为左指针
    if (map.size() == 3)
      int delId = Collections.min(map.values());
      map.remove(s.charAt(delId));
      start = delId+1;
    
    ans = Math.max(ans,end-start+1);
  
  return ans;

public static void main(String args[])

  String s = "ccaabbb";
  int data = new LengthOfLongestSubstringTwoDistinct().lengthOfLongestSubstringTwoDistinct(s);
  System.out.println(data);


4.长度最小的子数组(leetcode-209)

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

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

//输入:target = 11, nums = [1,1,1,1,1,1,1,1]
//输出:0
    //leetcode submit region begin(Prohibit modification and deletion)
class Solution 
    public int minSubArrayLen(int target, int[] nums) 

        // 定义需要维护的变量,包括最小长度和数组求和
        int ans = 999999,sum=0;
        // 定义数组首尾指针,并且滑动窗口(end指针移动)
        for (int start =0,end=0;end<nums.length;end++)
            // 更新需要维护的变量
            sum += nums[end];
            // 如果求和大于等于目标值则需要移动左指针,同时更新维护变量
            while(sum>=target)
                ans = Math.min(ans,end-start+1);
                sum -= nums[start];
                start+=1;
            
        
        if(ans==999999)
            return 0;
        
        return ans;
    

5.删除子数组的最大得分(leetcode-1659)

//给你一个正整数数组 nums ,请你从中删除一个含有 若干不同元素 的子数组。删除子数组的 得分 就是子数组各元素之 和 。返回 只删除一个 子数组可获得的 最大得分 。 如果数组 b 是数组 a 的一个连续子序列,即如果它等于 a[l],a[l+1],...,a[r] ,那么它就是 a 的一个子数组。

示例:
//输入:nums = [4,2,4,5,6]
//输出:17
//解释:最优子数组是 [2,4,5,6]
// 
//输入:nums = [5,2,1,2,5,2,1,2,5]
//输出:8
//解释:最优子数组是 [5,2,1] 或 [1,2,5]
    //leetcode submit region begin(Prohibit modification and deletion)
class Solution 
    public int maximumUniqueSubarray(int[] nums) 

        // step1:定义维护变量当前得分和最大得分
        int sum =0,sum_max = 0;
        Map<Integer,Integer> map = new HashMap<>();


        //step2:定义左右指针,并且移动右指针
        for (int start = 0,end =0;end<nums.length;end++)
            //step3:更新维护变量,哈希表验证是否存在重复值
            sum +=nums[end];
            map.put(nums[end],map.getOrDefault(nums[end],0)+1);
            // 判断不存在重复值,
            if (end-start+1 == map.size())
                sum_max = Math.max(sum,sum_max);
            
            //step4:如果子数组存在重复值,需要左侧收缩,直到窗口合法(确保窗口合法)
            while(end-start+1 > map.size())
                int head = nums[start];
                map.put(head,map.get(head)-1);
                if (map.get(head)==0)
                    map.remove(head);
                
                sum -= nums[start++];
            
        
        return  sum_max;
    

//leetcode submit region end(Prohibit modification and deletion)

6.找到字符串中所有字母异位词(leetcode-438)

//给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 
// 异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例:
//输入: s = "cbaebabacd", p = "abc"
//输出: [0,6]
//解释:
//起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
//起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
    //leetcode submit region begin(Prohibit modification and deletion)
class Solution 
    public List<Integer> findAnagrams(String s, String p) 


        //step1:定义维护变量,存储结果数组以及哈希表对比字符串是否为异位词
        List<Integer> ans = new ArrayList();
        Map<Character,Integer> map = new HashMap<>();
        Map<Character,Integer> mapp = new HashMap<>();


        //step1.1:构建字符串p的哈希表
        for (int i=0;i<p.length();i++)
            mapp.put(p.charAt(i),mapp.getOrDefault(p.charAt(i),0)+1);
        
        //step2:定义左右窗口指针,滑动右指针
        for (int start=0,end=0;end<s.length();end++)
            // step3:更新需要维护的变量,如果两个哈希表相等,则保存一个解
            map.put(s.charAt(end),map.getOrDefault(s.charAt(end),0)+1);
            if (map.equals(mapp))
                ans.add(start);
            


            //step4:由于窗口长度固定,if判断来维护固定长度窗口,并且左指针右移
            if (end-start+1>=p.length())
                map.put(s.charAt(start),map.get(s.charAt(start))-1);
                if (map.get(s.charAt(start))==0)
                    map.remove(s.charAt(start));
                
                start++;
            
        
        return ans;
    

//leetcode submit region end(Prohibit modification and deletion)

7. 字符串的排列(leetcode-567)


//给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。 
// 换句话说,s1 的排列之一是 s2 的 子串 。 

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

    /**
     * 思路:使用滑动窗口从s2中找到字符串s1的任意排列
     */


    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution 
        public boolean checkInclusion(String s1, String s2) 


            // step1:定义维护变量,哈希表存储字符串
            Map<Character,Integer> map = new HashMap<>();


            //step1.1:同时尽力字符串s1的哈希表
            Map<Character,Integer> maps1 = new HashMap<>();


            for (int i=0;i<s1.length();i++)
                maps1.put(s1.charAt(i),maps1.getOrDefault(s1.charAt(i),0)+1);
            


            // step2:定义窗口左右指针,并且移动右指针
            for (int start=0,end=0;end<s2.length();end++)
                //step3:更新维护变量,判断两个map知否相等决定是否包含
                map.put(s2.charAt(end),map.getOrDefault(s2.charAt(end),0)+1);
                if (map.equals(maps1))
                    return true;
                
                //step4:由于窗口长度固定,因此用if判断维护固定窗口,同时左指针右移动
                if (end-start+1>=s1.length())
                    //左指针移动,移动之前要更新维护变量
                    map.put(s2.charAt(start),map.get(s2.charAt(start))-1);
                    if (map.get(s2.charAt(start))==0)
                        map.remove(s2.charAt(start));
                    
                    start++;
                
            
            return false;
        
    
//leetcode submit region end(Prohibit modification and deletion)

8. 最大连续1的个数(leetcode-487,1004)


给定一个二进制数组,你可以最多将 1,k个 0 翻转为 1,找出其中最大连续 1 的个数。
示例 1:
输入:[1,0,1,1,0]
输出:4
解释:翻转第一个 0 可以得到最长的连续 1。
     当翻转以后,最大连续 1 的个数为 4。
 

注:
输入数组只包含 01.
输入数组的长度为正整数,且不超过 10,000
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution 
        public int longestOnes(int[] nums, int k) 
            //step1:定义维护变量,最大1个数以及哈希表统计0,1个数
            int max = 0;
            Map<Integer,Integer> map = new HashMap<>();
    
            // step2:定义左右指针,滑动右指针
            for (int start=0,以上是关于Python数据结构与算法篇-- 滑动窗口算法的主要内容,如果未能解决你的问题,请参考以下文章

算法和数据结构解析-5 : 滑动窗口问题

算法奥义:滑动窗口中位数与滑动魔方

聊聊算法——滑动窗口

聊聊算法——滑动窗口

滑动窗口算法

滑动窗口算法

(c)2006-2024 SYSTEM All Rights Reserved IT常识