算法学习1—同向双指针 滑动窗口

同向双指针 滑动窗口【基础算法精讲 01】_哔哩哔哩_bilibili

子数组、子串问题

209. 长度最小的子数组

209. 长度最小的子数组

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

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

每次添加一个元素到s中,然后进行是否左端点去除判断,以及判断s是否符合条件。 

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        n = len(nums)
        ans = n + 1  # 也可以写 inf
        s = left = 0
        for right, x in enumerate(nums):
            s += x
            # while s - nums[left] >= target:
            #     s -= nums[left]
            #     left += 1
            # if s >= target:
            #     ans = min(ans, right-left+1)
            while s >= target:  # 满足要求
                ans = min(ans, right - left + 1)
                s -= nums[left]
                left += 1
        return ans if ans <= n else 0

 713. 乘积小于 K 的子数组

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

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

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        ans = left = 0
        cnt = Counter()
        for right, c in enumerate(s):
            cnt[c] += 1
            while cnt[c] > 1:  # 不满足要求
                cnt[s[left]] -= 1
                left += 1
            ans = max(ans, right - left + 1)
        return ans

1004. 最大连续1的个数 III 

 1004. 最大连续1的个数 III

class Solution:
    def longestOnes(self, nums: List[int], k: int) -> int:
        ans = left = cnt0 = 0
        for right, x in enumerate(nums):
            cnt0 += 1 - x  # 0 变成 1,用来统计 cnt0
            while cnt0 > k:
                cnt0 -= 1 - nums[left]
                left += 1
            ans = max(ans, right - left + 1)
        return ans
        

遍历整个 nums 列表。对于当前位置 right,我们使用 x 存储该位置的值。然后,在 cnt0 中加上 1-x,即如果该位置为 0,则 cnt0 增加 1。

接着,我们进入一个 while 循环,该循环的目的是保证 cnt0 不超过 k,也就是我们最多将 k 个 0 变成 1。如果当前子串中 0 的个数大于 k,则我们需要将左端点 left 向右移动,直到当前子串中 0 的个数小于等于 k。为了实现这一点,我们从 cnt0 中减去 nums[left],同时将 left 加上 1。

最后,我们更新 ans 的值,使其等于当前连续 1 的子串的长度(即 right - left + 1)和之前计算得到的最长连续 1 子串长度中的较大值。在遍历完整个列表后,我们返回 ans 即可。

1234. 替换子串得到平衡字符串

1234. 替换子串得到平衡字符串

有一个只含有 'Q', 'W', 'E', 'R' 四种字符,且长度为 n 的字符串。

假如在该字符串中,这四个字符都恰好出现 n/4 次,那么它就是一个「平衡字符串」。

给你一个这样的字符串 s,请通过「替换一个子串」的方式,使原字符串 s 变成一个「平衡字符串」。

你可以用和「待替换子串」长度相同的 任何 其他字符串来完成替换。

请返回待替换子串的最小可能长度。

如果原字符串自身就是一个平衡字符串,则返回 0

根据题意,如果在待替换子串之外的任意字符的出现次数超过 m= 4 /n,那么无论怎么替换,都无法使这个字符的出现次数等于 m。

反过来说,如果在待替换子串之外的任意字符的出现次数都不超过 m,那么可以通过替换,使 s 为平衡字符串,即每个字符的出现次数均为 m。

这可以用同向双指针(长度不固定的滑动窗口)实现,具体原理可以看我的【基础算法精讲】,看完你就掌握同向双指针啦(APP 用户需要分享到 wx 打开)。

对于本题,设子串的左右端点为 left 和 right,枚举 right,如果子串外的任意字符的出现次数都不超过 m,则说明从 left 到 right 的这段子串可以是待替换子串,用其长度 right−left+1 更新答案的最小值,并向右移动 left,缩小子串长度。

class Solution:
    def balancedString(self, s: str) -> int:
        cnt, m = Counter(s), len(s) // 4
        if all(cnt[x] == m for x in "QWER"):  # 已经符合要求啦
            return 0
        ans, left = inf, 0
        for right, c in enumerate(s):  # 枚举子串右端点
            cnt[c] -= 1
            while all(cnt[x] <= m for x in "QWER"):
                ans = min(ans, right - left + 1)
                cnt[s[left]] += 1
                left += 1  # 缩小子串
        return ans

 

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

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

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

 

方法一:逆向思维+双指针

class Solution:
    def minOperations(self, nums: List[int], x: int) -> int:
        target = sum(nums) - x
        if target < 0: return -1  # 全部移除也无法满足要求
        ans = -1
        left = s = 0
        for right, x in enumerate(nums):
            s += x
            while s > target:  # 缩小子数组长度
                s -= nums[left]
                left += 1
            if s == target:
                ans = max(ans, right - left + 1)
        return -1 if ans < 0 else len(nums) - ans

 

方法二:直接双指针

如果要正向计算也是可以的,就是写起来稍微有点麻烦:首先算出最长的元素和不超过 x 的后缀,然后不断枚举前缀长度,另一个指针指向后缀最左元素,答案就是前缀+后缀长度之和的最小值

class Solution:
    def minOperations(self, nums: List[int], x: int) -> int:
        s, n = 0, len(nums)
        right = n
        while right and s + nums[right - 1] <= x:  # 计算最长后缀
            right -= 1
            s += nums[right]
        if right == 0 and s < x: return -1  # 全部移除也无法满足要求
        ans = n - right if s == x else inf
        for left, num in enumerate(nums):
            s += num
            while right < n and s > x:  # 缩小后缀长度
                s -= nums[right]
                right += 1
            if s > x: break  # 缩小失败,说明前缀过长
            if s == x: ans = min(ans, left + 1 + n - right)  # 前缀+后缀长度
        return ans if ans <= n else -1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值