• 0076-leeycode算法实现之最小覆盖子串-minimum-window-substring-python&golang实现


    给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

    注意:

    对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
    如果 s 中存在这样的子串,我们保证它是唯一的答案。

    示例 1:

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

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

    输入: s = "a", t = "aa"
    输出: ""
    解释: t 中两个字符 'a' 均应包含在 s 的子串中,
    因此没有符合条件的子字符串,返回空字符串。

    提示:

    1 <= s.length, t.length <= 105
    s 和 t 由英文字母组成

    进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?

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

    python

    class Solution:
        def minWindow(self, s: str, t: str) -> str:
            """
            滑动窗口,时间:右指针遍历一次s串,左指针最多遍历一次完整串,即O(2n)->O(n),空间:有限变量+s与t共有的元素O(k),常量级即O(1)
            思路:
            1.need哈希表记录t中元素及出现次数,needCnt表示t中元素长度或个数,通过这两个变量可以知道加入的元素,没有加入的元素,就不需要每次窗口遍历need获取哪些元素还需加进窗口
              在遍历s的过程中,遍历的元素,每次遍历到的value--,即s-t的元素value都是负数,0表示刚好没有,而 s&t的元素,小于0多余,等于0刚好,大于0需要移动右指针扩大窗口
            2.移动右指针,扩大窗口,加入新元素
            3.移动左指针,缩小窗口,寻找当前窗口的最小
            4.当左指针已经是当前右界的最小窗口的左界时,右移左指针,寻找下一个符合条件的窗口
            算法:
            1.初始化准备工作:
            -初始化need哈希表,value=0,遍历t,记录字符及个数,
            -needCnt初始化为当前t的长度,
            -初始化左指针i在0处,
            -初始化返回结果res,右上下界组成,初始化为0到无穷大
            2.右指针j右移
            -优先判断是否遇到t中元素,是则needCnt--
            -need对应字符的value--
            -检查当前窗口是否覆盖t中元素,进入3步骤
            3.needCnt==0表示当前窗口覆盖t中元素,可以通过左指针右移缩小窗口以获得局部最小覆盖窗口
            -检查当前左指针字符,如果need中字符value为0,即当前为局部最小最优解,跳出循环
                -不等于0,即小于0,窗口可以优化,need中value++,左指针右移,重复上面及此步骤
            -已经是局部最优解,检查与res的大小,取较小值记录,每次记录都应该在左窗口右移缩小时记录
            -寻找下一个符合条件的窗口,然后优化窗口寻找局部最优
                -当前元素的need value++,needCnt++,左指针i++右移
            重复以上步骤
            4.如果没有找到res即res还是初始化的上下界,返'',否则返回最小覆盖子串
            """
            from collections import defaultdict
            need=defaultdict(int) # 初始化哈希表,用于存储遍历的窗口元素及出现次数
            for c in t: # 遍历字符t,遇到字符即+1,遍历完即为t中所有元素出现次数的统计
                need[c]+=1
            needCnt=len(t) # needCnt用于统计遍历窗口中是否全部包含了t的元素
            i=0 # 初始化窗口左边界指针,窗口右减左加
            res=(0,float('inf')) # 初始化返回结果,0 到 无穷大
            for j,c in enumerate(s): # 枚举或遍历字符串s, j为下标,c为字符
                if need[c]>0: # 检查字符c次数是否大于0,即遇到t中元素,needCnt--,True表示窗口可以继续右移,j++
                    needCnt-=1 # 由于只有在t中的元素出现在s中,其value才为正,needCnt--表示此次出现一次,其他非t中字符出现在s中,不进此逻辑判断
                need[c]-=1 # 不管遇到什么字符,遍历到就--,非t中元素出现在s中,肯定是多余,也一定是负数
                if needCnt==0:       #步骤一:滑动窗口包含了所有T元素,>>> 由于neddCnt表示t中字符长度或个数,如果为0,表示当前窗口下,已经包含有所有t中字符,否则j++
                    while True:      #步骤二:增加i,排除多余元素 >>> 通过不断地右移左指针,缩小当前窗口长度,排除非t中字符
                        c=s[i] # 当前在s中的窗口左指针字符
                        if need[c]==0: # 检查左指针字符对应的need哈希value是否等于0,不等于0,肯定小于0,多余字符,可右移缩小窗口,假设移动了该字符,由于是左指针,相当于减小窗口,need[c]++,如果大于0,说明窗口还需要扩大,但右指针当前固定为j,所以当前窗口可以作为临时合要求的结果
                            break # 和符合要求的窗口,应该退出左指针右移
                        need[c]+=1 # 如果不等于0,说明左指针指向的字符是多余字符,对应字符value++
                        i+=1 # 上面移动左指针因为是多余字符,可以继续右移左指针, i++
                    if j-i<res[1]-res[0]:   #记录结果 >>> 只要小都应该记录,而且更新,相当于更新窗口长度,同时也是固定了位置
                        res=(i,j)
                    need[s[i]]+=1  #步骤三:i增加一个位置,寻找新的满足条件滑动窗口 >>> 当前窗口是包含t中元素的最小窗口,后面左指针元素的need的value++,表示左指针字符次数++,表示需要加入的t中元素
                    needCnt+=1 # 肯定有某个t中的元素需要在新窗口中加入
                    i+=1 # 右移左指针,寻找新的符合条件的窗口
            return '' if res[1]>len(s) else s[res[0]:res[1]+1] #如果res始终没被更新过,代表无满足条件的结果 >>> 如果res没有更新,res[1] = inf,无穷大,就是没有找到合适的窗口,否则返回最小的子串
    
    if __name__ == "__main__":
        s = 'usyiskbleasydknitehw'
        t = 'sky'
        test = Solution()
        print(test.minWindow(s,t))
    

    golang

    package main
    
    import "fmt"
    
    func main()  {
    	s := "ADOBECODEBANC"
    	t := "ABC"
    	fmt.Println(minWindow(s,t))
    }
    
    func minWindow(s string, t string) string {
    	ori, cnt := map[byte]int{}, map[byte]int{}
    	for i := 0; i < len(t); i++ {
    	    ori[t[i]]++
    	}
      
    	sLen := len(s)
    	len := math.MaxInt32
    	ansL, ansR := -1, -1
      
    	check := func() bool {
    	    for k, v := range ori {
    		  if cnt[k] < v {
    			return false
    		  }
    	    }
    	    return true
    	}
    	for l, r := 0, 0; r < sLen; r++ {
    	    if r < sLen && ori[s[r]] > 0 {
    		  cnt[s[r]]++
    	    }
    	    for check() && l <= r {
    		  if (r - l + 1 < len) {
    			len = r - l + 1
    			ansL, ansR = l, l + len
    		  }
    		  if _, ok := ori[s[l]]; ok {
    			cnt[s[l]] -= 1
    		  }
    		  l++
    	    }
    	}
    	if ansL == -1 {
    	    return ""
    	}
    	return s[ansL:ansR]
    
  • 相关阅读:
    嵌入式Linux基础知识
    面向对象程序设计与原则
    设计模式之工厂模式
    设计模式之单例模式
    基于Doxygen的C/C++注释原则
    设计模式概论
    重启博客
    java 单例模式的几种写法
    CountDownLatch、CyclicBarrier
    java ConcurrentHashMap和CopyOnWriteArrayList解决并发问题
  • 原文地址:https://www.cnblogs.com/davis12/p/15418217.html
Copyright © 2020-2023  润新知