【基础算法精选03】滑动窗口……

3 阅读6分钟

题目:209. 长度最小的子数组

解法: 滑动窗口,遍历右节点,左节点根据条件缩小窗口,具体见代码实现。

时间复杂度: O(n)n 为数组 nums 的长度,while 内操作数小于等于 n,而不是每次操作 n 个,所以默认为常量。

空间复杂度: O(1) 代码实现:

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        # 滑动窗口
        ans = inf
        left = s = 0
        n = len(nums)
        for right, x in enumerate(nums):
            s += x
            while s >= target:
                ans = min(right - left + 1, ans)
                s -= nums[left]
                left += 1
        
        return 0 if ans == inf else ans

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

解法: 思路同上,滑动窗口,遍历右指针,将搜索的字符进行计数 +1,当该字符计数 >1 时,循环缩小窗口直至无重复该字符的窗口,right - left + 1 即为当前窗口的长度,返回和上次窗口相比更大的值,具体见代码实现。

时间复杂度: O(n),n 为字符串 s 的长度

空间复杂度: O(1) 代码实现:

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        cnt = Counter()
        ans = left = 0

        for right, c in enumerate(s):
            cnt[c] += 1
            while cnt[c] > 1:
                cnt[s[left]] -= 1
                left += 1
            ans = max(right - left + 1, ans)
        return ans

题目:713. 乘积小于 K 的子数组

解法: 滑动窗口,遍历右指针,获取乘积,当 s >= k 时,s/=nums[left] & left++ 循环缩小窗口直至符合条件,将所有结果进行累加 返回即可,见代码实现。特别的, k<=1 时,nums中 不存在小于 1 的子数组,返回0即可。

时间复杂度: O(n)

空间复杂度: O(1)

代码实现:

class Solution:
    def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
        if k <= 1:
            return 0
        left = ans = 0
        s = 1
        for right, x in enumerate(nums):
            s *= x
            while s >= k:
                s /= nums[left]
                left += 1
            ans += right - left + 1
        return ans

题目:3090. 每个字符最多出现两次的最长子字符串

解法: 只需要在第3.无重复字符的代码上更改循环的条件即可,见代码实现

时间复杂度: O(n)

空间复杂度: O(1)

代码实现:


class Solution:
    def maximumLengthSubstring(self, s: str) -> int:
        cnt = Counter()
        ans = left = 0

        for right, c in enumerate(s):
            cnt[c] += 1
            while cnt[c] > 2:
                cnt[s[left]] -= 1
                left += 1
            ans = max(right - left + 1, ans)
        return ans

题目:2958. 最多 K 个重复元素的最长子数组

解法:3. 无重复字符的最长子串3090. 每个字符最多出现两次的最长子字符串

时间复杂度: O(n)

空间复杂度: O(1)

代码实现:

class Solution:
    def maxSubarrayLength(self, nums: List[int], k: int) -> int:
        cnt = Counter()
        left = ans = 0

        for right, x in enumerate(nums):
            cnt[x] += 1
            while cnt[x] > k:
                cnt[nums[left]] -= 1
                left += 1
            ans = max(right - left + 1, ans)
        return ans

题目:2730. 找到最长的半重复子字符串

解法:3. 无重复字符的最长子串3090. 每个字符最多出现两次的最长子字符串2958. 最多 K 个重复元素的最长子数组

时间复杂度: O(n)

空间复杂度: O(1)

代码实现:


class Solution:
    def longestSemiRepetitiveSubstring(self, s: str) -> int:
        
        ans = left = cnt = 0

        for right, c in enumerate(s):
            if right and c == s[right - 1]:
                cnt += 1
            while cnt > 1:
                out = s[left]
                left += 1
                if out == s[left]:
                    cnt -= 1
            ans = max(right - left + 1, ans)
        return ans

题目:1004. 最大连续1的个数 III

解法:3. 无重复字符的最长子串3090. 每个字符最多出现两次的最长子字符串2958. 最多 K 个重复元素的最长子数组

时间复杂度: O(n)

空间复杂度: O(1)

代码实现:

class Solution:
    def longestOnes(self, nums: List[int], k: int) -> int:
        # 求连续1 的最大长度 -> 求连续 0 的最大长度
        ans = left = cnt0 = 0
        for right, x in enumerate(nums):
            if x == 0:
                cnt0 += 1
            while cnt0 > k:
                if nums[left] == 0:
                    cnt0 -= 1
                left += 1
            ans = max(right - left + 1, ans)
        return ans

题目:2962. 统计最大元素出现至少 K 次的子数组

解法:2730. 找到最长的半重复子字符串

时间复杂度: O(n)

空间复杂度: O(1)

代码实现:


class Solution:
    def countSubarrays(self, nums: List[int], k: int) -> int:
        mx = max(nums)
        left = ans = cnt_mx = 0
        for right, x in enumerate(nums):
            if x == mx:
                cnt_mx += 1
            while cnt_mx >= k:
                out = nums[left]
                if out == mx:
                    cnt_mx -= 1
                left += 1
            ans += left
        return ans

题目:2302. 统计得分小于 K 的子数组数目

解法:209. 长度最小的子数组713. 乘积小于 K 的子数组713. 乘积小于 K 的子数组

时间复杂度:

空间复杂度:

代码实现:

class Solution:
    def countSubarrays(self, nums: List[int], k: int) -> int:
        ans = left = s = 0
        for right, x in enumerate(nums):
            s += x
            while s * (right - left + 1) >= k:
                s -= nums[left]
                left += 1
            ans += right - left + 1
        return ans

题目:1658. 将 x 减到 0 的最小操作数

解法: 从两侧减数,意味着 中心部分的 target 是连续的,可以设定target = sum(nums) - x ,求和为target的最大长度 ans,余下的就是预期的最小长度 len(nums) - ans

时间复杂度: O(n)

空间复杂度: O(1)

代码实现:

class Solution:
    def minOperations(self, nums: List[int], x: int) -> int:
        target = sum(nums) - x
        if target < 0:
            return -1
        
        left = s = 0
        ans = -1
        for right, x in enumerate(nums):
            s += x
            while s > target:
                s -= nums[left]
                left += 1
            if s == target:
                ans = max(right - left + 1, ans)
        return ans if ans == -1 else len(nums) - ans


题目:3795. 不同元素和至少为 K 的最短子数组长度

解法:2730. 找到最长的半重复子字符串 ,但本题中的 while 循环中有坑点,① if cnt[x] == 1: s += x ② 收缩左节点时 cnt-1 ,当 cnt0 时,s -= out

时间复杂度: O(n)

空间复杂度: O(1)

代码实现:

class Solution:
    def minLength(self, nums: List[int], k: int) -> int:
        left = s = 0
        cnt = Counter()
        ans = inf
        for right, x in enumerate(nums):
            cnt[x] += 1
            if cnt[x] == 1:
                s += x
            while s >= k:
                ans = min(right - left + 1, ans)
                out = nums[left]
                cnt[out] -= 1
                if cnt[out] == 0:
                    s -= out
                left += 1
        return -1 if ans == inf else ans


题目:76. 最小覆盖子串

解法:2730. 找到最长的半重复子字符串3795. 不同元素和至少为 K 的最短子数组长度

时间复杂度: O(n)

空间复杂度: O(1)

代码实现:

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        cnt_t = Counter(t)
        cnt_s = Counter()

        ans_left, ans_right = -1, len(s)
        left = 0
        for right, c in enumerate(s):
            cnt_s[c] += 1
            while cnt_s >= cnt_t:
                if right - left < ans_right - ans_left:
                    ans_left, ans_right = left, right
                cnt_s[s[left]] -= 1
                left += 1
        return "" if ans_left == -1 else s[ans_left: ans_right + 1]