典型的滑动窗口问题。
初步想法是先遍历一遍字符串计算四个字母出现的频率,然后计算平均值获得一个字母应该出现的个数(这个个数当然也可以直接通过长度/4算出来。。。)
别人写的博客使用的是暴力法,直接按照最小窗口长度遍历字符串,不断增大窗口。也即是先用窗口为一的从头到尾遍历,然后再用2的遍历,十分暴力。
另外一个方法是使用滑动窗口算法,先来看看滑动窗口问题代码设置的思路:
- 初始化窗口:使用两个指针(
left和right)来表示窗口的边界。- 扩展窗口:将
right指针向右移动,扩展窗口,直到窗口内的元素满足某个条件。- 缩小窗口:如果窗口内的元素满足条件,尝试将
left指针向右移动,缩小窗口,以找到更小的满足条件的子串。- 记录结果:在每次缩小窗口时,记录当前窗口的大小,并更新最小窗口大小。
所以最关键的是要想清楚窗口内满足的条件到底是什么?这个也是最难想到的地方。
题目的要求是:处理完一个字串后,四个字母的数量将相同。这个情况仅在窗口外部的字母数量小于理想值的前提下才能成立。假设窗口外的A的数量大于理想值,无论窗口内如何操作都无法满足题目的要求。
因此在本题中,活动窗口的条件为:窗口外的字母出现的次数不大于理想值。转化为具体操作就是 <font style="color:rgb(51, 51, 51);background-color:rgb(248, 248, 248);">totalCount[i] - windowCount[i] > idealFreq</font>。这个外我认为是本题的精髓所在,当我们考虑活动窗口需要满足的条件时,不应该仅仅考虑窗口内的状态,在某些情况下也可以通过窗口外的状态判断是否记录结果。
用Go语言实现
具体而言,在编写滑动窗口的题目时,会先初始化两个指针,然后以右指针作为循环的判断条件对输入切片遍历。
为了判断窗口外的字母数量是否小于理想值,分别创建两个切片windowCount ,totalCount 记录窗口内的字母数量以及整个字符串的字母数量,以此通过两者的差值获取窗口外的字母数量。
package main
import (
"fmt"
)
func solution(input string) int {
// 构造字母到数字的索引
charToIndex := make(map[byte]int)
charToIndex['A'] = 0
charToIndex['S'] = 1
charToIndex['D'] = 2
charToIndex['F'] = 3
totalCount := make([]int, 4)
length := len(input)
for i := 0; i < length; i++ {
totalCount[charToIndex[input[i]]]++
}
// Please write your code here
windowCount := make([]int, 4)
// 计算理想值
ideaCount := length / 4
minLen := length
// 开始使用滑动窗口技术寻找最小值
left, right := 0, 0
for right < length {
windowCount[charToIndex[input[right]]]++
right++
for left < right && isIdea(ideaCount, windowCount, totalCount) {
minLen = min(right-left, minLen)
windowCount[charToIndex[input[left]]]--
left++
}
}
fmt.Println(minLen)
return minLen
}
// 辅助函数,用于判断当前窗口是否满足条件
func isIdea(ideaCount int, windowCount []int, totalCount []int) bool {
for i := 0; i < 4; i++ {
if totalCount[i]-windowCount[i] > ideaCount {
return false
}
}
return true
}
func main() {
// You can add more test cases here
fmt.Println(solution("ADDF") == 1)
fmt.Println(solution("ASAFASAFADDD") == 3)
}