滑动窗口
- 前言
- 一、替换子串得到平衡字符串
- 二、map计数+滑动窗口
- 总结
- 参考文献
前言
对于子串问题,确定左边界和有边界,就能确定一个子串,暴力取子串,时间复杂度O(n2)。有时挖掘内在规律的限定,或者问题所限定,就可以采用滑动窗口寻找子串,时间复杂度O(n)。
一、替换子串得到平衡字符串
二、map计数+滑动窗口
// 目的:待替换子串的最小可能长度
// 这个子串里应该包含超过len(s) >> 2的字符串的多余个数。
// 确定一个最小窗口,使得外面的每种字符都小于n/4即可(更简单的逆向判断)。
// 滑动窗口
func balancedString(s string) int {
// 初始化每个字符对应的数组下标,便于统计。
fx := map[byte]int{'Q':0,'W':1,'E':2,'R':3}
// 记录每个字符出现的次数
cnt := [4]int{}
for i := 0;i < len(s);i++ {
cnt[fx[s[i]]]++
}
n := len(s) >> 2
// 已经平衡
if isOk(cnt,n) {
return 0
}
// 滑窗记录最小替换子串
left,right,m := 0,0,len(s)
for ;right < len(s);right++ {
cnt[fx[s[right]]]--
if !isOk(cnt,n) {
continue
}
// 一旦圈住了多余的字符,就移动左边界,寻找最短多余子串。
for ;left <= right && isOk(cnt,n);left++ {
cnt[fx[s[left]]]++
}
// 记录最短可替换多余子串。
m = min(m,right - left + 2)
}
return m
}
// 窗口外的每类字符数是否都不超过n
func isOk(cnt [4]int,n int) bool {
for _,v := range cnt {
if v > n {
return false
}
}
return true
}
func min(x,y int) int {
if x < y {
return x
}
return y
}
总结
1)问题转换,通过对字符的计数,以及计数的修改,来判断字符内外的情况。
2)逆向判定,通过窗口外的字符情况,来判断窗口内是否包含了所有多余字符,再左边界右滑,把可替换子串压缩到最小。
3)选择的可能性很多,我们需要挖掘其中规律及限定,来计算有效的可能性。例如O(n)的滑窗就oK了,而不是O(n方)来确定子串是否有效。
参考文献
[1] LeetCode 替换子串得到平衡字符串