560. 和为 K 的子数组
给你一个整数数组 nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的子数组的个数 。
子数组是数组中元素的连续非空序列。
示例 1:
**输入:nums = [1,1,1], k = 2
输出:2
示例 2:
**输入:nums = [1,2,3], k = 3
输出:2
双层循环,i,j表示子数组的左、右边界,遍历所有的子串,判断其和是否等于k。O(N x N)
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
cnt, n = 0, len(nums)
for i in range(n):
sum = 0
for j in range(i, n):
sum += nums[j]
if (sum == k): cnt += 1
return cnt
另一种思路是前缀和,差分法可以求任意子序列的和。
前缀和类似于数据积分,prefix[i]表示子数组0~i的和,prefix[i] - prefix[j]表示j+1, i之间的和。通常prefix初始化时长度为n+1,此时prefix[0]表示空数组的长度,即一个元素也没有;这样prefix[1] - prefix[0]才是只有一个元素时的前缀和。
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
cnt, n = 0, len(nums)
pre = [0] * (n + 1) # 初始化
for i in range(1, n + 1):
pre[i] = pre[i - 1] + nums[i - 1]
for i in range(1, n + 1):
for j in range(i, n + 1):
if (pre[j] - pre[i - 1] == k): cnt += 1
return cnt
记录个数,方便复用,同时减少不必要的循环。
class Solution:
def subarraySum(self, nums, k):
result = 0
sum = 0
prefix = {0: 1}
for n in nums:
sum += n
if (sum - k) in prefix:
result += prefix[sum - k]
prefix[sum] = prefix.get(sum, 0) + 1
return result
239. 滑动窗口最大值
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
固定窗口,循环遍历,找到所有符合长度条件的子数组,确定子数组内的最大值,保存在结果数组中。
另一种方法,采用优先队列/大顶堆,遍历元素,维护长度=k的结构,保存在数组中。
import heapq
heapq默认是小顶堆,通过对元素取负,转换成小顶堆,最后翻转得到目标值。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
# 注意 Python 默认的优先队列是小根堆
q = [(-nums[i], i) for i in range(k)]
heapq.heapify(q)
ans = [-q[0][0]]
for i in range(k, n):
heapq.heappush(q, (-nums[i], i))
while q[0][1] <= i - k:
heapq.heappop(q)
ans.append(-q[0][0])
return ans
76. 最小覆盖子串
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:**最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。
class Solution:
def minWindow(self, s: str, t: str) -> str:
if not t or not s:
return ""
# 字典t_dict记录字符串t中每个字符的数量
t_dict = {}
for char in t:
t_dict[char] = t_dict.get(char, 0) + 1
# 需要找到的字符数量
required = len(t_dict)
# 左右指针初始化
l, r = 0, 0
# formed用于记录当前窗口中满足t_dict字符数量的字符种类数量
formed = 0
# 字典window_counts记录当前窗口中每个字符的数量
window_counts = {}
# (窗口长度, 左指针, 右指针)
ans = float("inf"), None, None
while r < len(s):
# 扩大窗口
character = s[r]
window_counts[character] = window_counts.get(character, 0) + 1
# 如果当前字符的数量满足了t_dict中的要求,则formed增加
if character in t_dict and window_counts[character] == t_dict[character]:
formed += 1
# 当formed等于required,说明当前窗口已经覆盖了t中的所有字符
while l <= r and formed == required:
character = s[l]
# 更新答案
if r - l + 1 < ans[0]:
ans = (r - l + 1, l, r)
# 缩小窗口
window_counts[character] -= 1
if character in t_dict and window_counts[character] < t_dict[character]:
formed -= 1
l += 1
# 移动右指针
r += 1
return "" if ans[0] == float("inf") else s[ans[1]:(ans[2] + 1)]
我们使用两个指针 l
和 r
来表示滑动窗口的左右边界。通过移动这两个指针,我们探索所有可能的窗口以找到最小的覆盖子串。我们还使用了两个字典 t_dict
和 window_counts
来分别跟踪 t
中的字符及其数量以及当前窗口中的字符及其数量。通过比较这两个字典,我们可以知道当前窗口是否包含了 t
中的所有字符。变量 formed
用于跟踪当前窗口中满足 t
字符数量要求的字符种类数。当 formed
等于 required
时,我们知道当前窗口是一个有效的覆盖子串,并尝试通过移动左指针来缩小它的大小。我们不断更新最小覆盖子串的长度和位置,直到找到答案或遍历完整个字符串 s
。