同向双指针 滑动窗口【基础算法精讲 01】_哔哩哔哩_bilibili
子数组、子串问题
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
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
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
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. 替换子串得到平衡字符串
有一个只含有 '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 的最小操作数
给你一个整数数组 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