560. 和为 K 的子数组
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。
示例:
输入:nums = [1,2,3], k = 3
输出:2
Solution
前缀和的定义是 prefix[i] = nums[0] + nums[1] + … + nums[i]。如果我们知道了 prefix[i] 和 prefix[j],那么我们可以得到 subarray[i,j] = prefix[j] - prefix[i] 的和。
为了解决这个问题,我们可以初始化一个 hashmap,用于储存每个前缀和的出现次数。在遍历数组的过程中,我们同时也在计算前缀和,并检查 hashmap 中是否存在当前前缀和减去 k 的值,如果存在,就说明存在一个子数组和为 k,那么我们就可以将结果加上该前缀和的出现次数。
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
prefix_sum = 0
dic = {
0: 1} # 前缀和为0的子数组有一个,就是空数组
count = 0
for num in nums:
prefix_sum += num
if prefix_sum - k in dic:
count += dic[prefix_sum - k]
dic[prefix_sum] = dic.get(prefix_sum, 0) + 1
return count
在尝试增加哈希表 dic 中的某个键值对时,你可能会遇到该键不存在的情况,我们需要使用 dict.get() 方法来避免这个错误。dict.get(key, default) 方法将返回字典中给定键的值。如果键不在字典中,它将返回默认值。
239. 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
Solution
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
left = 0
right = left + k - 1
res = []
while right < len(nums):
res.append(max(nums[left: right + 1]))
left += 1
right += 1
return res
然而,当你的窗口大小(k)和nums列表的长度相近或者非常大的时候,该算法会变得非常慢,因为对于每个滑动窗口,你都在使用O(k)时间复杂度的max()函数。这种情况下,你可能会遇到超时的问题。
为了优化「最大值」,我们可以想到一种非常合适的数据结构,那就是大根堆。Python内置的heapq模块只支持小根堆,所以我们需要存储元素的负值以实现大根堆的功能。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
heap = [(-nums[i], i) for i in range(k)]
heapq.heapify(heap)
res = [-heap[0][0]]
for i in range(k, len(nums)):
# 添加新元素到堆中
heapq.heappush(heap, (-nums[i], i))
# 删除滑动窗口之外的元素
while heap[0][1] <= i - k:
heapq.heappop(heap)
# 堆顶元素即为当前滑动窗口的最大值
res.append(-heap[0][0])
return res
这个解决方案的时间复杂度为O(nlogk),其中n是nums列表的长度。空间复杂度为O(k),用于存储滑动窗口的元素。
你还可以使用一种数据结构叫做双端队列 (deque) 来在O(1)时间复杂度内获取滑动窗口的最大值。在Python中,collections模块提供了deque。这个数据结构允许你在两端添加或删除元素。
在这个问题中,你可以维护一个双端队列,其中存储的是元素的索引,而队列中的元素按照对应的nums值降序排列。队列的第一个元素总是当前滑动窗口的最大值。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
res = []
# deque里存的是nums中的index,而且deque中index对应的值是按照从大到小排列的
d = deque()
for i, n in enumerate(nums):
while d and nums[d[-1]] < n: # 如果deque不为空且deque最后一个index对应的值小于当前值
d.pop() # 删除deque中最后一个值
d.append(i)
if d[0] == i - k: # 如果deque中第一个index已经不在窗口内
d.popleft() # 删除deque中第一个index
if i >= k - 1: # 当滑动窗口形成后
res.append(nums[d[0]]) # 将deque中第一个index对应的值添加到结果中,因为它就是当前滑动窗口的最大值
return res
这个解决方案的时间复杂度是O(n),空间复杂度是O(k)。
76. 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
示例:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。
Solution
- 先将字符串 t 中的字符及其出现次数保存到一个哈希表中(这个哈希表可以使用Python的字典来实现)。
- 初始化一个滑动窗口,窗口的左右边界都设置为字符串 s 的起始位置。
- 扩大窗口的右边界,直到窗口内的字符包含了字符串 t 的所有字符。
- 在保持窗口内的字符包含字符串 t 的所有字符的情况下,尽可能地缩小窗口的左边界。
- 记录当前窗口的大小,并更新最小窗口大小和最小窗口的位置。
- 重复步骤3-5,直到窗口的右边界到达字符串 s 的末尾。
class Solution:
def minWindow(self, s: str, t: str) -> str:
t_counter = Counter(t)
current_counter = Counter()
min_len = float('inf')
min_window = ''
left = 0
for right, char in enumerate(s):
current_counter[char] += 1
while current_counter >= t_counter:
if right - left + 1 < min_len:
min_window = s[left: right + 1]
min_len = right - left + 1
current_counter[s[left]] -= 1
left += 1
return min_window
然而,current_counter >= t_counter 实际上会对窗口进行一次完全的遍历,而且我们在 current_counter 存了所有可能出现的character,而不只是 t 所需要的 character。
我们可以引入一个新的变量 valid,用来记录 t 中在滑动窗口中满足数量要求的字符个数。只有当 valid 为 len(t_counter) 时,我们才尝试移动left指针。这样可以让我们更有效率地移动left指针,避免在不必要的字符上浪费时间。
class Solution:
def minWindow(self, s: str, t: str) -> str:
t_counter = Counter(t)
current_counter = Counter()
left, right = 0, 0
valid = 0
start, length = 0, float('inf')
while right < len(s):
c = s[right]
right += 1
if c in t_counter:
current_counter[c] += 1
if current_counter[c] == t_counter[c]:
valid += 1
while valid == len(t_counter):
if right - left < length:
start, length = left, right - left
d = s[left]
left += 1
if d in t_counter:
if current_counter[d] == t_counter[d]:
valid -= 1
current_counter[d] -= 1
return '' if length == float('inf') else s[start: start + length]