滑动窗口|最小覆盖子串&字符串的排列&无重复最长子串
滑动窗口算法的思路是这样:
-
1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right) 称为一个「窗口」。
-
2、我们先不断地增加 right 指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
-
3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。
-
4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。
使用cntMap优化子串判断条件,统计每个子串需要出现的次数,以及需要几个不同的字母nums
时间复杂度:O(m+n),m,n分别为字符串m和n的长度
空间复杂度:O(n)
func getSubStr(s, t string) string { cntMap := make(map[byte]int, len(t)) nums := 0 for _, c := range []byte(t) { if cntMap[c] == 0 { nums++ // 统计t字符串中有几种字母出现 } cntMap[c]++ //记录t字符串中每个字母需要出现的次数 } ans1, ans2 := -1, len(s) //初始化最优解的左右窗口下标 left := 0 //从[0,0]区间开始,右指针不断右移 for right, c := range []byte(s) { cntMap[c]-- // 对应次数减1 if cntMap[c] == 0 { //该字母满足出现次数要求 nums-- //子串需要的字母总数减少一个 } for nums == 0 { // 开始找最优解,直到子串字母需要出现的次数都满足, if right-left < ans2-ans1 { // 找到更短的子串 ans1, ans2 = left, right // 记录此时的左右端点 } x := s[left] // 左端点字母 if cntMap[x] == 0 { // x 移出窗口之前,检查出现次数,x字符移走后,次数要+1 nums++ } cntMap[x]++ // 左端点字母移出子串 left++ } } //左指针没有移动过,最大化窗口也不满足条件,没有找到 if ans1 < 0 { return "" } return s[ans1 : ans2+1] }
题意:该题等价于,s1的每种字符的出现次数,与 s2 某个子串的每种字符的出现次数相同。假设该子串的位置为[start,end],使用滑动窗口找到该区间,并且该区间内字符出现次数相同即可
题解:使用两个数组记录两个子串每个字符出现的次数,这个次数作为辅助使用,用于快速判断是否存在次数相对的子串。使用滑动窗口匹配出现次数
func checkInclusion(s1 string, s2 string) bool { count1, count2 := [26]int{}, [26]int{} // 两个长度为26的数组 for i := 0; i < len(s1); i++ { // 统计s1的字符的出现次数 count1[s1[i]-'a']++ } start := 0 // 指向s2子串的开头 for i, c := range s2 { // 遍历s2的字符 count2[c-'a']++ // 将s2的字符统计到count2 // 如果start上的字符,count2的比count1多,这说明start不是合格子串的开头,start应该步进,舍弃这个字符 for start <= i && count1[s2[start]-'a'] < count2[s2[start]-'a'] { count2[s2[start]-'a']-- start++ } // 如果两个count数组全等,说明找到了满足条件的子串 if count1 == count2 { // go里面数组是基本类型,可以这么比较 return true } } // 考察了所有s2的字符,都没有找到合适的子串 return false }
最长无重复子串
题解:
从头开始遍历字符串,对每一个遍历过的字符判断是否被标记过,若标记过,则更新左边界le=vis[temp]+1,同时更新当前不重复子串的长度;否则用数组下标进行标记,不断右移右边界ri,更新最大长度
时间复杂度:O(n)
空间复杂度:O(n)
func lengthOfLongestSubstring(s string) int { le, ri, maxLen, ans := 0, 0, 0, 0 vis := make([]int, 256) for i := range vis { vis[i] = -1 } for ri < len(s) { temp := s[ri] if vis[temp] >= le { le = vis[temp] + 1 maxLen = ri - le } vis[temp] = ri ri++ maxLen++ if ans < maxLen { ans = maxLen } } return ans }
题解:使用滑动窗口,不断调整右指针,直到满足字符数量,然后在调整左指针找最优解
func findAnagrams(s, p string) (ans []int) { cnt := [26]int{} // 统计 p 的每种字母的出现次数 for _, c := range p { cnt[c-'a']++ } left := 0 for right, c := range s { c -= 'a' cnt[c]-- // 右端点字母进入窗口 for cnt[c] < 0 { // 字母 c 太多了 cnt[s[left]-'a']++ // 左端点字母离开窗口 left++ } if right-left+1 == len(p) { // s' 和 p 的每种字母的出现次数都相同 ans = append(ans, left) // s' 左端点下标加入答案 } } return }
复杂度分析
- 时间复杂度:O(m),其中 m 是 s 的长度
- 空间复杂福:O(n)
等风起的那一天,我已准备好一切
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
2019-12-23 1003 Emergency (25分) 求最短路径的数量