Python版-LeetCode 学习:76. 最小覆盖子串

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。

示例:输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:如果 S 中不存这样的子串,则返回空字符串 ""。
如果 S 中存在这样的子串,我们保证它是唯一的答案。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring

方法1:该方法主要使用的是滑动窗口思想,大概含义:

用left,right表示滑动窗口的左边界和右边界,通过改变i,j来扩展和收缩滑动窗口,可以想象成一个窗口在字符串上游走,当这个窗口包含的元素满足条件,即包含字符串T的所有元素,记录下这个滑动窗口的长度right-left  +1,这些长度中的最小值就是要求的结果

具体实现过程如下:

(1)计算目标值t中每个char的个数,作为char的需求清单need,即需要need中k*v个char;

         用处:当检索到滑动窗口中存在一个char时,就抵消掉need中对应的char值,即need[char] -1 ;当检索到窗口函数中不存在一个char时,就增加need中对应的char值,即need[char] +1 ;

(2)记录目标值t的长度,needCnt,即总共需要的字符串数,needCnt等于need中value的总和。

         用处:取代遍历need,通过总数来判断滑动窗口是否包含了所有的t中char

(3)不断增加指针right使滑动窗口增大,直到窗口包含了T的所有元素:当窗口包含了所有的need字符(即needCnt=0),意味着可以剔除窗口括进来的不需要字符,这时可以缩小窗口了

(4)不断增加left,使滑动窗口缩小。因为是要求最小字串,所以将不必要的元素排除在外,使长度减小,直到碰到一个必须包含的元素(其实是清单need中必须包含的子串不能小于0,当=0时,意味着需求刚好满足),这个时候不能再扔了。再扔就不满足条件了,记录此时滑动窗口的left和right的值,right-left  +1,即是最小子串的长度。并保存最小值。

(5)当right再增加一个位置,滑动窗口就不满足条件的时候,这是就要移动left,是滑动窗口增大,直至窗口包含了所有的need字符(即needCnt=0)。这时从步骤(3)从新来,寻找新的满足条件的滑动窗口,如此反复,直到left超出了字符串S范围。

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        # 目标字符记录
        need=collections.defaultdict(int)
        for c in t:
            need[c]+=1
        # 目标字符长度
        needCnt=len(t)
        left=0
        res=(0,float('inf'))
        for r,c in enumerate(s):
            if need[c]>0:
                # 如果need[c]>0,意味着窗口搜索到目标值
                needCnt-=1
            # 目标值被搜索到,则need相应value-1,即用以记录搜索到need中字符的个数;
            # =0代表搜索到的值和need值相同;<0代表need中同一个字符被多次搜索到;
            # >0,代表need中该字符没有全部被遍历出来
            need[c]-=1   
            
            if needCnt==0: # 滑动窗口包含了所有T元素  
                # 开始窗口函数内部的搜索
                while True:  # 移动滑动窗口右边界i,排除多余元素
                    c=s[left] 
                    if need[c]==0:  
                        # need[c]==0,break 情况,代表着这轮的滑动窗口不符合要求
                        break
                    # 窗口中字符和need相互抵消
                    need[c]+=1
                    r+=1
                if r-left len(s) else s[res[0]:res[1]+1]    #如果res始终没被更新过,代表无满足条件的结果

总结:该类题目主要就是利用left和right俩套指针循环,形成一个可滑动的窗口,将目标值t与滑动窗口进行比较。主要的难点也在如何进行滑动窗口和目标值的比较。我们利用记账簿的形式,将需要的字符记录为字符整数集need,每满足一个目标值就减1.在这个过程中,我们还记录了滑动窗口中的每个字符,这些不需要的字符都记为负值。当他们被移除滑动窗口时,这些字符就记为0.那些从滑动窗口添加进need的char,其中不是目标值的char的value永远都不会为正。

当need中所有的字符value都小于等于0时,即需求被满足了。而维护一个额外的变量needCnt的作用是,来记录所需元素的总数量,当我们碰到一个所需元素c,不仅need[c]的数量减少1,同时needCnt也要减少1,这样我们通过needCnt就可以知道是否满足条件,这样就无需遍历need字典了。
前面也提到过,need记录了遍历到的所有元素,不是目标值的char的value永远都不会为正,而只有need[c]>0时,代表c就是所需元素,当need中所有char的value都是0时,则代表所有的目标值都被满足,且滑动窗口中没有多余的字符。
链接:https://leetcode-cn.com/problems/minimum-window-substring/solution/tong-su-qie-xiang-xi-de-miao-shu-hua-dong-chuang-k/

另参考:

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        need = collections.defaultdict(int)
        for c in t:
            need[c] += 1
        needCnt = len(t)
        i = 0 #记录起始位置
        res = (0, float('inf'))  #用两个元素,方便之后记录起终点
        #三步骤:
        #1. 增加右边界使滑窗包含t
        for j,c in enumerate(s):
            if need[c] >0:
                needCnt -= 1
            need[c] -= 1 #这行放在外面不可以,看19行 need[c] == 0
        #2. 收缩左边界直到无法再去掉元素   !注意,处理的是i
            if needCnt == 0:
                while True:
                    c = s[i]
                    if need[c] == 0: #表示再去掉就不行了(need>0)
                        break
                    else:
                        need[c] += 1
                        i += 1
                if j-i < res[1] - res[0]:  #这里是否减一都可以,只要每次都是这样算的就行,反正最后也是输出子串而非长度
                    res = (i,j)
        #3. i多增加一个位置,准备开始下一次循环(注意这步是在 needCnt == 0里面进行的 )
                need[s[i]] += 1
                needCnt += 1    #由于 移动前i这个位置 一定是所需的字母,因此NeedCnt才需要+1
                i += 1
        return "" if res[1]>len(s) else s[res[0]: res[1]+1]

 

你可能感兴趣的:(LeetCode算法,字符串,python,数据结构,算法)