[Leetcode] [Tutorial] 子串


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

  1. 先将字符串 t 中的字符及其出现次数保存到一个哈希表中(这个哈希表可以使用Python的字典来实现)。
  2. 初始化一个滑动窗口,窗口的左右边界都设置为字符串 s 的起始位置。
  3. 扩大窗口的右边界,直到窗口内的字符包含了字符串 t 的所有字符。
  4. 在保持窗口内的字符包含字符串 t 的所有字符的情况下,尽可能地缩小窗口的左边界。
  5. 记录当前窗口的大小,并更新最小窗口大小和最小窗口的位置。
  6. 重复步骤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]

猜你喜欢

转载自blog.csdn.net/weixin_45427144/article/details/131327286