• 打败算法 —— 最小覆盖子串


    本文参考

    出自LeetCode上的题库 —— 最小覆盖子串,本篇解法主要在官方题解的基础上做一定修改

    https://leetcode-cn.com/problems/minimum-window-substring/

    最小覆盖子串问题

    给定一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串,如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""
    对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量

    示例1:
    输入: s = "ADOBECODEBANC", t = "ABC"
    输出: "BANC"

    示例2:
    输入:s = "a", t = "a"
    输出:"a"

    示例3:
    输入:s = "a", t = "aa"
    输出:""

    滑动窗口简介

    基本概念:
    滑动窗口控制左右两个指针,通常情况下,每次控制其中一个指针向前或向后移动,最典型的应用就是TCP协议中的发送窗口,它同时受到流量控制策略和拥塞控制策略的影响

    适用范围:
    对有序的字符串或列表求最值或子序列,例如本题应用滑动窗口求最小字串

    基本步骤:
    1、初始化左右指针的下标索引 l_index = r_index = 0,索引闭区间 [l_index, r_index] 称为一个窗口

    2、不断地增加 r_index 指针扩大窗口 [l_index, r_index],直到窗口中的序列符合要求

    3、此时,停止增加 r_index,转而不断增加 l_index指针缩小窗口 [l_index, r_index],直到窗口中的序列不再符合要求

    4、重复第 2 和第 3 步,直到 r_index 到达序列的尽头

    解题思路

    按照滑动窗口的基本解题步骤,关键点在于在增大r_index 指针位置的过程中,如何判断当前的窗口已经包含 t 中所有的字符。我们可以简单的通过Python预置的map数据结构构造字符串 t 的"(字符,字符数量)"键值对,每次移动 r_index 指针后,利用map检查窗口中的字符种类和字符个数是否已经满足要求。若满足要求,记录当前的 l_index 指针和字符串长度后,开始移动 l_index ,缩减窗口大小,检查是否存在更短的覆盖字串;若不满足要求,则继续移动 r_index

    滑动窗口解法

    class Solution:
      ori = dict()
      cnt = dict()

      def check(self) -> bool:
      """
        判断cnt是否已经包含ori中应有的字符和字符对应的个数

      :return bool
      """
      for
    key, value in self.ori.items():
        if self.cnt.get(key, -1) < value:
          return False
        return True

      def
    min_window_1(self, s: str, t: str) -> str:
        self.ori.clear()
        self.cnt.clear()

        # t 中字符计数

        for c in t:
          self.ori[c] = self.ori.setdefault(c, 0) + 1

          # 初始化指针

          left = 0
          right = 0
          ans_left = -1
          length = sys.maxsize

          while right < len(s):
            cur = s[right]

            if cur in self.ori.keys():
              self.cnt[cur] = self.cnt.setdefault(cur, 0) + 1

            若窗口满足要求,则移动左指针

                    
    while self.check() and left <= right:
              if right - left + 1 < length:
                length = right - left + 1
                ans_left = left
              if s[left] in self.ori.keys():
                self.cnt[s[left]] -= 1
              # 继续缩减窗口大小

              left += 1
            # 移动右指针

            right += 1

        return '' if ans_left == -1 else s[ans_left:ans_left+length]

    需要注意的是,因为LeetCode判题使会不断地调用 ori 和 cnt 两个 map ,为了防止各测试用例间造成干扰,需要先用 clear() 函数清空上一轮存储的字符数据

    实际上,我们可以加快左指针的移动速度,直接移动到字符串 t 包含的字符的位置上,而不是使窗口每次只缩减一个字符的长度,完善后的代码如下:

    def min_window_2(self, s: str, t: str) -> str:
      self.ori.clear()
      self.cnt.clear()

      # t 中字符计数

      for c in t:
        self.ori[c] = self.ori.setdefault(c, 0) + 1

      # 初始化指针

      l_index = 0
      r_index = 0
      ans_l_index = -1
      length = sys.maxsize
      # 记录左指针应该跳转的位置

      record = list()

      # 滑动窗口

      while r_index < len(s):
        cur = s[r_index]

        if cur in self.ori.keys():
          self.cnt[cur] = self.cnt.setdefault(cur, 0) + 1
          record.append(r_index)

        # 若窗口满足要求,则移动左指针

        while self.check() and l_index <= r_index:
          # 快速移动左指针

          l_index = record.pop(0)
          if r_index - l_index + 1 < length:
            length = r_index - l_index + 1
            ans_l_index = l_index
          self.cnt[s[l_index]] -= 1
          # 移动右指针

          r_index += 1

      return '' if ans_l_index == -1 else s[ans_l_index:ans_l_index+length]

  • 相关阅读:
    【iCore3 双核心板】例程二十四:LAN_DHCP实验——动态分配IP地址
    【iCore3 双核心板】例程二十三:LAN_HTTP实验——网页服务器
    【iCore3 双核心板】例程二十二:LAN_UDP实验——以太网数据传输
    【iCore3 双核心板】例程二十一:LAN_TCPS实验——以太网数据传输
    【iCore3 双核心板】例程二十:LAN_TCPC实验——以太网数据传输
    【iCore3 双核心板】例程十九:USBD_MSC实验——虚拟U盘
    【iCore3 双核心板】例程十八:USB_VCP实验——虚拟串口
    【iCore3 双核心板】例程十七:USB_MSC实验——读/写U盘(大容量存储器)
    【iCore3 双核心板】例程十六:USB_HID实验——双向数据传输
    【iCore3 双核心板】例程十五:USB_CDC实验——高速数据传输
  • 原文地址:https://www.cnblogs.com/kuluo/p/15945437.html
Copyright © 2020-2023  润新知